Compare commits

...

663 Commits

Author SHA1 Message Date
Oskari Lavinto
767871ff60 Translated using Weblate (Finnish)
Currently translated at 98.3% (478 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2022-03-16 06:13:37 -04:00
Oskari Lavinto
8b46d3e126 Translated using Weblate (Finnish)
Currently translated at 55.7% (271 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2022-03-16 01:13:35 -04:00
cameron
b723ddc250 wip fixes 2022-03-14 19:09:38 +01:00
cameron
c721e37988 fixed next item being marked watched, fixed subtitles not clearing from prev episode 2022-03-14 19:09:12 +01:00
wolong gl
e317ce3093 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (486 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-03-14 06:13:33 -04:00
Bill Thornton
0c435e4112 Merge pull request #1519 from nielsvanvelzen/change-user-while-listening-to-music-crash 2022-03-13 00:11:26 -05:00
Vardas
1ff9604151 Translated using Weblate (Lithuanian)
Currently translated at 15.8% (77 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/lt/
2022-03-12 18:13:33 -05:00
Niels van Velzen
970d165b74 Fix app crash when switching users while music is playing 2022-03-12 21:49:18 +01:00
David Fairbrother
2af623ed13 Fix a potential crash in VideoManager with invalid speed
We should never hit this, unless someone is manually tweaking values in
the debugger.... but it still results in a hard crash as %d is for an
int, not a double.
2022-03-12 20:32:00 +01:00
Niels van Velzen
7e038334f8 Add ripple effect to DefaultCardView 2022-03-11 08:51:00 +01:00
Niels van Velzen
dcd8486d6c Fix ripple effect on LegacyImageCardView overflowing 2022-03-11 08:51:00 +01:00
Niels van Velzen
c26ffdfbfa Close ServerFragment when the server is removed from preferences 2022-03-11 08:50:38 +01:00
Niels van Velzen
fac5a7d35e Hide "manage servers" in preferences when there are no servers 2022-03-11 08:50:38 +01:00
WWWesten
7bf1f9b38b Translated using Weblate (Russian)
Currently translated at 100.0% (486 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-03-10 06:13:31 -05:00
cameron
42b3d469d1 remove tvapp.getapplication 2022-03-10 09:31:56 +01:00
gradle-update-robot
37315878e2 Update Gradle Wrapper from 7.4 to 7.4.1.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2022-03-10 08:47:48 +01:00
Bill Thornton
64248639a0 Merge pull request #1508 from mueslimak3r/crash-fix 2022-03-09 21:05:57 -05:00
cameron
aa80ca561f crash fix from null item 2022-03-09 14:34:28 -08:00
Ali Dastpour
007fc70a8c Translated using Weblate (Persian)
Currently translated at 100.0% (486 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 17:13:31 -05:00
Ali Dastpour
7d3a31b9de Translated using Weblate (Persian)
Currently translated at 98.7% (480 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 13:13:34 -05:00
Ali Dastpour
1a98e41e1e Translated using Weblate (Persian)
Currently translated at 52.2% (254 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 06:46:28 -05:00
Ali Dastpour
a21463fa44 Translated using Weblate (Persian)
Currently translated at 52.0% (253 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 06:37:03 -05:00
Ali Dastpour
69da942ed9 Translated using Weblate (Persian)
Currently translated at 51.2% (249 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 06:24:44 -05:00
Ali Dastpour
0fcfa2028e Translated using Weblate (Persian)
Currently translated at 50.4% (245 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 06:17:56 -05:00
Ali Dastpour
8f23c5975c Translated using Weblate (Persian)
Currently translated at 49.7% (242 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 06:14:41 -05:00
Ali Dastpour
64594dcec3 Translated using Weblate (Persian)
Currently translated at 45.8% (223 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 06:04:01 -05:00
Ali Dastpour
db9cc8a836 Translated using Weblate (Persian)
Currently translated at 41.7% (203 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 05:46:38 -05:00
Ali Dastpour
ffc5da8e6b Translated using Weblate (Persian)
Currently translated at 40.5% (197 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 05:35:24 -05:00
Ali Dastpour
071f0d06d0 Translated using Weblate (Persian)
Currently translated at 39.5% (192 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 05:30:54 -05:00
Ali Dastpour
b3bf36eb43 Translated using Weblate (Persian)
Currently translated at 37.8% (184 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-09 05:23:28 -05:00
Ali Dastpour
b610572f18 Translated using Weblate (Persian)
Currently translated at 35.8% (174 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-08 20:13:32 -05:00
Ali Dastpour
6e99c563cd Translated using Weblate (Persian)
Currently translated at 25.3% (123 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-08 17:16:01 -05:00
Ali Dastpour
86081c03db Translated using Weblate (Persian)
Currently translated at 20.9% (102 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-08 16:58:42 -05:00
Ali Dastpour
08e940dd50 Translated using Weblate (Persian)
Currently translated at 14.4% (70 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-03-08 16:34:25 -05:00
Cameron
e60aba4424 require at least server version 10.8.0 to use HLS (#1501)
* proof of concept

* refactored

* renamed method

* use comparison operator overload, use toString
2022-03-08 16:44:56 +01:00
cameron
36e4e949fe don't use exoplayer track selection if the track ID can't be parsed 2022-03-08 08:50:52 +01:00
cameron
40ac7ac7bf check if subtitle track info is empty 2022-03-08 08:50:52 +01:00
Bill Thornton
0ec0df73a8 Merge pull request #1502 from nielsvanvelzen/leanback-channel-worker-catch-timeout 2022-03-07 22:25:29 -05:00
kazuki ishii
f4afda0766 Translated using Weblate (Japanese)
Currently translated at 71.8% (349 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ja/
2022-03-07 18:13:30 -05:00
Thomas Schwery
6e6306f3db Translated using Weblate (French)
Currently translated at 100.0% (486 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-03-07 18:13:30 -05:00
dependabot[bot]
070ef78e22 Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 18:39:49 +01:00
dependabot[bot]
67a8333331 Bump actions/stale from 4.1.0 to 5
Bumps [actions/stale](https://github.com/actions/stale) from 4.1.0 to 5.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v4.1.0...v5)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 18:39:40 +01:00
Niels van Velzen
30ab16e11b Catch SDK exceptions in LeanbackChannelWorker 2022-03-07 16:59:16 +01:00
Sayykii
ce03220c29 Translated using Weblate (Bulgarian)
Currently translated at 31.8% (155 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bg/
2022-03-07 07:13:30 -05:00
axel
7f6701d3cf Translated using Weblate (French)
Currently translated at 99.7% (485 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-03-06 19:13:30 -05:00
millallo
3ac6ad9d28 Translated using Weblate (Italian)
Currently translated at 100.0% (486 of 486 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2022-03-06 04:13:30 -05:00
Weblate
44156ece1e Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2022-03-05 14:45:23 -05:00
Niels van Velzen
57ec029bd6 Update default home sections and remove rewatching type 2022-03-05 20:45:20 +01:00
Oatavandi
f04adce784 Translated using Weblate (Tamil)
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2022-03-05 13:13:31 -05:00
nextlooper42
346c376408 Translated using Weblate (Slovak)
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-03-05 13:13:30 -05:00
Niels van Velzen
31b30b4b01 Translated using Weblate (Dutch)
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2022-03-05 13:13:30 -05:00
Niels van Velzen
b2a000251d Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2022-03-05 13:13:30 -05:00
Lukáš Kucharczyk
cc76f45dc7 Translated using Weblate (Czech)
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-03-05 13:13:29 -05:00
WWWesten
c671ee1229 Translated using Weblate (Esperanto)
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-03-05 07:53:31 -05:00
El Koulali András
67760232a7 Translated using Weblate (Hungarian)
Currently translated at 100.0% (487 of 487 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2022-03-05 07:53:31 -05:00
Weblate
0f0d2458a7 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2022-03-05 07:53:31 -05:00
Niels van Velzen
f6274f4549 Lower minimum subtitle size to 10 2022-03-05 13:53:27 +01:00
Niels van Velzen
81b973f6b6 Check mFragment for null value in PlaybackController 2022-03-05 13:53:14 +01:00
Weblate
bc9a7ca585 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2022-03-04 16:44:52 -05:00
Bill Thornton
955dea63d2 Merge pull request #1491 from nielsvanvelzen/longpress-crash 2022-03-04 16:44:49 -05:00
Niels van Velzen
a7558823bc Fix an issue where long-touching can crash the app 2022-03-04 22:38:28 +01:00
Weblate
41a3334153 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2022-03-04 16:21:31 -05:00
Bill Thornton
2f16b17a79 Merge pull request #1490 from nielsvanvelzen/dislike
Remove like & dislike
2022-03-04 16:21:28 -05:00
Niels van Velzen
17c45e6145 Remove like & dislike 2022-03-04 22:11:08 +01:00
Bill Thornton
1e39f86801 Merge pull request #1489 from nielsvanvelzen/longpress-menu
Open context menu on long press
2022-03-04 16:09:04 -05:00
Niels van Velzen
0163e00162 Open context menu on long press 2022-03-04 22:01:06 +01:00
Niels van Velzen
de6f84cbe8 Fix various Kotlin warnings 2022-03-03 22:19:31 +01:00
Niels van Velzen
3501038aa9 Explicitly set namespace and application id in build file 2022-03-03 08:41:38 +01:00
Niels van Velzen
9dd2f05ca6 Update AGP to 7.1.2 2022-03-03 08:41:38 +01:00
theGUI001
4fa0228478 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2022-03-02 14:13:28 -05:00
小造xu_zh
15c1786c6a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-03-02 09:13:28 -05:00
WWWesten
af4652982b Translated using Weblate (Hebrew)
Currently translated at 98.9% (488 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2022-03-01 16:13:28 -05:00
Shahar DynaZor
7ed836de83 Translated using Weblate (Hebrew)
Currently translated at 98.9% (488 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2022-03-01 13:13:30 -05:00
cameron
5f9f984ac2 bumped SLIDESHOW_DURATION to 30 seconds 2022-03-01 16:26:43 +01:00
cameron
aea04dd077 fixed background cycling 2022-03-01 16:26:43 +01:00
nextlooper42
bbd5a7ba7a Translated using Weblate (Slovak)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-02-28 06:13:27 -05:00
nextlooper42
16ecee2245 Translated using Weblate (Slovak)
Currently translated at 99.7% (492 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-02-27 19:13:27 -05:00
Ocuriz
c7ce74d125 Translated using Weblate (French)
Currently translated at 99.5% (491 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-02-26 21:26:00 -05:00
Ali Dastpour
cc44334019 Translated using Weblate (Persian)
Currently translated at 13.5% (67 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2022-02-26 11:13:28 -05:00
Cameron
320924e4b2 use ts for exoplayer transcodes (#1466)
* use ts for exoplayer

* removed addAc3Streaming helper

* removed findTranscodingProfile
2022-02-26 09:49:58 +00:00
Cameron
8b57f188e2 use track id for exoplayer track selection instead of guessing (#1469)
* fixed exoplayer track selection

* renamed a variable and changed some comments
2022-02-26 10:38:01 +01:00
Niels van Velzen
fa97f4e453 Migrate FullDetailsActivity to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
cf5a65d668 Migrate TextSearchFragment to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
aa138ac8ab Migrate PhotoPlayerActivity to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
5f626359d9 Migrate ItemListActivity and DetailRowView to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
b9027e62c3 Migrate AudioNowPlayingActivity to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
dc5966da1a Migrate TextUnderButton to view binding
Also removed a lot of unused stuff from it
2022-02-26 10:36:22 +01:00
Niels van Velzen
1360632e35 Migrate NumberSpinner to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
710e43002c Migrate NowPlayingBug to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
e203601a9f Migrate MyDetailsOverviewRowPresenter to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
879ff91afc Migrate ItemRowView to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
45c77cb1f5 Migrate ItemListView to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
89c379e19a Migrate HorizontalGridPresenter to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
f0714bb965 Migrate GridFragment to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
6807455e71 Migrate EnhancedBrowseFragment to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
ff1d735579 Migrate AudioDelayPopup to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
cec1e91b5f Migrate StdGridFragment alphapicker to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
12556343da Migrate NextUpButtons to view binding 2022-02-26 10:36:22 +01:00
Niels van Velzen
d978be4bff Update process lifecycle to avoid startup crash 2022-02-26 10:35:10 +01:00
Oatavandi
bcb1d0a271 Translated using Weblate (Tamil)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2022-02-25 16:00:22 -05:00
Lukáš Kucharczyk
5adc74ccda Translated using Weblate (Czech)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-02-25 16:00:22 -05:00
Lukáš Kucharczyk
db9efc9b0a Translated using Weblate (Czech)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-02-24 12:24:16 -05:00
cameron
299d64d4a0 subtitle fixes 2022-02-24 13:36:39 +01:00
Adam Zvolanek
3ef023afac Translated using Weblate (Czech)
Currently translated at 99.7% (492 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-02-23 21:50:57 -05:00
Csaba
1ef034de46 Translated using Weblate (Hungarian)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2022-02-23 11:13:26 -05:00
WWWesten
762279e4d6 Translated using Weblate (Esperanto)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-02-23 08:13:26 -05:00
WWWesten
63968aa201 Translated using Weblate (Russian)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-02-23 08:13:26 -05:00
WWWesten
a431f7b026 Translated using Weblate (Kazakh)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-02-23 08:13:26 -05:00
WWWesten
54b651c1fa Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2022-02-23 08:13:26 -05:00
Niels van Velzen
bf1ad6d9da Add 10.8 home section types 2022-02-23 11:50:28 +01:00
Niels van Velzen
450d778454 Disable wildcard imports 2022-02-23 11:45:57 +01:00
lyaschuchenko
b3a75867d6 Translated using Weblate (Ukrainian)
Currently translated at 60.8% (299 of 491 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2022-02-22 09:13:28 -05:00
lyaschuchenko
e035aac7bb Translated using Weblate (Ukrainian)
Currently translated at 31.9% (157 of 491 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2022-02-21 03:13:25 -05:00
Jim Ma
e91484d007 Translated using Weblate (Chinese (Traditional))
Currently translated at 69.8% (343 of 491 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hant/
2022-02-20 07:13:25 -05:00
Jim Ma
5d3cf69a14 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (491 of 491 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-02-20 07:13:24 -05:00
Niels van Velzen
ec8cdedd4f Fix remaining context used before initialization crashes 2022-02-20 12:58:43 +01:00
Cameron
8424e58158 Subtitle rendering optimization (#1455)
* wip render subtitles once per event

* tidied up

* changed wording of debug log

* wip render subtitles once per event

* tidied up

* changed wording of debug log

* added a comment

* hide debug log for the last event unless its useful
2022-02-20 11:39:12 +01:00
Bill Thornton
b23a7bb5e4 Merge pull request #1457 from nielsvanvelzen/context-crash
Fix crash when opening video player
2022-02-19 13:31:09 -05:00
Niels van Velzen
7a605501a9 Fix crash when opening video player 2022-02-19 19:16:23 +01:00
Niels van Velzen
22c50d50f5 Rewrite all unit tests using kotest 2022-02-19 19:01:41 +01:00
Niels van Velzen
2cfd134a83 Remove unused koin-test dependencies 2022-02-19 19:01:41 +01:00
Niels van Velzen
203bcfa13d Move all unit tests to kotlin sourceset, convert TimeUtilsTest.java to TimeUtilsTest.kt 2022-02-19 19:01:41 +01:00
Niels van Velzen
b40efcd845 Receive context in MediaManager.playNowInternal 2022-02-19 11:19:31 +01:00
Weblate
c0875f0717 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2022-02-19 04:16:13 -05:00
Niels van Velzen
b0c71018f3 Remove some dead code in error handler from ItemRowAdapter 2022-02-19 10:16:09 +01:00
Niels van Velzen
4126f07c6b Close Home and Details activities when no active user found
This prevents the app from crashing after recovering from a crash. It's a temporary solution until the current user data is moved to a repository and the activities are rewritten to make use of that.
2022-02-19 10:16:00 +01:00
Niels van Velzen
8cd28ccf0d Require context for BaseRowItem.getBadgeImage 2022-02-19 10:15:38 +01:00
Niels van Velzen
c6a29166b4 Use closest context instead of TvApp.getApplication in other places 2022-02-19 10:15:38 +01:00
Niels van Velzen
d119261f6c Use getString using closest context
Removes TvApp.getApplication usages when getting strings where possible
2022-02-19 10:15:38 +01:00
Niels van Velzen
d2e09b1d56 Disable blank issues 2022-02-16 21:25:43 +01:00
WWWesten
3347922a7d Translated using Weblate (Esperanto)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-02-16 07:13:23 -05:00
WWWesten
0cc481eb0a Translated using Weblate (Russian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-02-16 07:13:23 -05:00
WWWesten
e8d9b7e725 Translated using Weblate (Kazakh)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-02-16 07:13:23 -05:00
Lukáš Kucharczyk
62b0323f64 Translated using Weblate (Czech)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-02-14 02:13:22 -05:00
Ocuriz
c22a31566d Translated using Weblate (French)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-02-13 11:13:22 -05:00
val
f4527691ec Translated using Weblate (Estonian)
Currently translated at 6.3% (31 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/et/
2022-02-12 10:13:22 -05:00
cameron
694cd02fbf fix subtitle loading when not transcoding, and burnt in subs not turning off 2022-02-12 13:06:10 +01:00
Cameron
ddca2c9813 exoplayer track selection for audio (#1432)
* proof of concept exoplayer track selection for subs and audio

* replaced break with return for clarity - no change in behavior

* fixed translation of stream indexes to handle weird subtitle/audio arrangements

* forgot to add brackets around an if

* removed subtitle specific code

* prevent re-initializing in videoManager, cleaned up, added comments

* reverted subtitle changes in PlaybackController and VideoManager

* styling

* build upon TrackSelectionOverrides in stopPlayback - no functional difference (yet)

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java

* added annotations and null checks for streamType

* removed a comment
2022-02-12 13:04:44 +01:00
cameron
013c8f6d85 reverted VideoAdapter getDuration 2022-02-12 12:03:59 +01:00
Niels van Velzen
65e53d340a Update AGP to 7.1.1 2022-02-12 12:02:11 +01:00
Cameron
6a0710d85f show buffered position in seekbar for exoplayer (#1435)
* show buffered position in seekbar for exoplayer

* removed debug log

* removed TODO

* reworked getDuration, added utility function for getting a safe seek position

* renamed getSafePosition to getSafeSeekPosition

* removed check for finishedInitialSeek to do mFragment.setCurrentTime
2022-02-12 10:06:28 +01:00
newton181
2df46ff4fc Translated using Weblate (Spanish (Latin America))
Currently translated at 95.7% (471 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2022-02-11 17:13:22 -05:00
Niels van Velzen
09423e29d3 Remove enableFeaturePreview for version catalogs 2022-02-09 19:04:27 +01:00
gradle-update-robot
9b7f6f7236 Update Gradle Wrapper from 7.3.3 to 7.4.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2022-02-09 19:04:27 +01:00
hoanghuy309
ca4f9146f6 Translated using Weblate (Vietnamese)
Currently translated at 98.1% (483 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2022-02-09 06:13:20 -05:00
siankatabg
a231cc31f3 Translated using Weblate (Bulgarian)
Currently translated at 29.4% (145 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bg/
2022-02-08 12:13:21 -05:00
Oskari Lavinto
10e561c8fe Translated using Weblate (Finnish)
Currently translated at 38.0% (187 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2022-02-08 08:13:20 -05:00
cameron
001706708a reset vlc meta position in clearPlaybackSessionOptions 2022-02-08 08:54:36 +01:00
Andrew Lee
28a99226f2 Translated using Weblate (Chinese (Traditional))
Currently translated at 69.9% (344 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hant/
2022-02-08 00:13:20 -05:00
Cameron
e268dbdbb8 fix crashes from changing audio tracks, fix exoplayer not using default audio track (#1409)
* fixed vlc audio track resetting to default, maybe fixed crash when using non default

* fixed audio track issues with libvlc and exo related to transcoding

* typo

* account for equality comparison of non-null obj to null obj

* removed one condition

* wip fixes - libVLC audio stream resetting on seek still broken

* fixed seeking resetting audio index in libVLC - again

* removed some debug logging, fixed libVLC direct play startup

* test

* added back startProgressLoop that was accidentally deleted

* moved debug output inside if so it's clearer whats going on

* cleaned up the code, added comments, removed some debug logs
2022-02-07 17:12:46 +01:00
Niels van Velzen
29623721fa Always use device info from SDK 2022-02-07 07:34:10 +01:00
Niels van Velzen
3c7d6692c4 Fix AutoBitrate causing an app crash when switching users while detecting bitrate 2022-02-07 07:33:49 +01:00
Theofilos Moupasiridis
3e452ba8f3 Translated using Weblate (Greek)
Currently translated at 81.9% (403 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2022-02-06 08:13:19 -05:00
cameron
24d7196774 revert to player.eventlistener 2022-02-05 22:38:57 +01:00
Niels van Velzen
df4b098861 Use Timber instead of (legacy) apiclient logger in StopTranscodingResponse 2022-02-05 18:15:55 +01:00
Niels van Velzen
2c1ec238a5 Fix opening app via leanback channels not showing item 2022-02-05 14:34:34 +01:00
Niels van Velzen
eed8458f3f Check active user from sessionRepository in AuthenticatedUserCallbacks 2022-02-05 14:34:21 +01:00
Niels van Velzen
8e110fcc05 Prevent DreamActivity (screensaver) from opening login screen 2022-02-05 14:34:21 +01:00
Juanpi Suarez
d25311c5fb Translated using Weblate (Spanish (Argentina))
Currently translated at 86.3% (425 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2022-02-05 03:13:19 -05:00
Emil Manea
9a785229fc Translated using Weblate (Romanian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2022-02-04 15:13:20 -05:00
Cameron
06fe4d2ecc simplified exoplayer code (#1401)
* simplified exoplayer code


replaced TvApp.getApplication(), replaced Player.EventListener()

removed data and media source factories

* reworked configureExoplayerFactory to return the exoplayer builder

* fixed typo
2022-02-04 08:58:55 +01:00
Cameron
2dcd0d1019 bug report template - changed examples to encourage providing more specific info (#1420)
* Update bug-report.yaml

* Update .github/ISSUE_TEMPLATE/bug-report.yaml
2022-02-04 08:56:47 +01:00
Cameron
15fd2466e1 playback bug report template - changed examples to encourage providing more specific info (#1419)
* Update bug-report-playback.yaml

* Update .github/ISSUE_TEMPLATE/bug-report-playback.yaml
2022-02-04 08:56:40 +01:00
DavidFair
9e74f2dc7a Include Jellyfin Exoplayer FFmpeg dep (#1369)
* Include Jellyfin Exoplayer FFmpeg dep

Port the includes from the Android repo as-is into this repo to include
the dependency

Convert our initialisation to prefer the built in extensions. This
should avoid flaky device logic and matches the Android app choice too.

* Drop check for DCS / DCA on firestick gen 1

FFmpeg should handle the decoding on these devices for us natively, and
likely uses the same codepath as libVLC. This should help us stick with
the native player instead of VLC where possible

* Replace TvApp.getApplication with getting context from activity

This removes another deprecated usage of TvApp by the reviewer

* Add new codecs to list of supported profiles

Adds the new codecs to reflect the new capabilities available from our
FFmpeg extensions.

* Refactor duplicated audio codec list into method

Refactor the duplicated audio codecs. Whilst I can't think of a case
where a user might play say DTS / TrueHD audio files, I equally can't
think of a reason why not to support it.

This simplifies the code, whilst allowing us to still extend the list
with some Audio only codecs for audio direct-play

* Lowercase PCM formats

This matches the format found on the server and the Android mobile
implementation to be safe

* AudioDirectPlayProfile: Replace vaarg with Array<String>

This allows us to easily combine two arrays together without having to
create copies with spread operators.
Internally the copies still function the same as we have an interable
type we are applying joinToString(",") with

* Switch to using EXTENSION_RENDERER_MODE_ON

This will use the system core renderers first. To get the fallback
behaviour we simply needed to add `setEnabledDecoderFallback`. This
allows Exoplayer to try the first decoder, then silently fallback on
error to ffmpeg.

Functionally this matches the implementation goals in the Android Mobile where
they will manually fallback instead. Since we can't access / restart the
media queue easily in this repo.

* Code cleanup from PR review

Addresses various comments where we can improve the readability around
the various changes we've made

* Drop ExoPlayerProfileTests

Since it only check hardcoded values which rarely change are there

* Whitespace tidy up + manual reformatting

Some various tidy ups and manual reformatting as pointed out from the
PR. This helps reduce down the amount of empty whitespace in the file

* Formatting fixes

Various hand-fixes where the formatter hasn't quite got it right
2022-02-03 19:37:31 -08:00
edupoma
2132da187f Translated using Weblate (Catalan)
Currently translated at 89.8% (442 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2022-02-03 20:13:19 -05:00
Emil Manea
c978ee58ae Translated using Weblate (Romanian)
Currently translated at 76.2% (375 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2022-02-03 20:13:19 -05:00
edupoma
94ccc84083 Translated using Weblate (Spanish)
Currently translated at 89.8% (442 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2022-02-03 20:13:18 -05:00
Niels van Velzen
f05bf646cc Remove getCurrentActivity usages in VideoManager and PlaybackController 2022-02-03 09:06:12 +01:00
David Fairbrother
490f17019b Move video speed controls to the right of secondary bar
Closed Captions and Subtitles are used far more than this. Especially
since the controller will preserve your speed between videos.
2022-02-03 08:53:26 +01:00
Niels van Velzen
8c01a3d0eb Remove unused codefrom data.compat package (#1417) 2022-02-03 08:43:33 +01:00
Niels van Velzen
fddac39403 Remove "i" prefix from interfaces (#1416)
* Remove "i" prefix from interfaces

* Remove unnecessary GridLoader interface
2022-02-03 08:43:08 +01:00
Bill Thornton
4fa4a18087 Fix default values for library sorting 2022-02-02 22:04:36 +01:00
Bill Thornton
803bf5496c Merge pull request #1412 from nielsvanvelzen/never-call-updatesession-callback-twice
Fix ApiBinder calling callback twice when the success callback fails
2022-02-02 11:13:11 -05:00
Niels van Velzen
fa3a76037f Fix ApiBinder calling callback twice when the success callback fails 2022-02-02 16:54:43 +01:00
Niels van Velzen
ee8dfdb2ee Don't use TextUtilsKt.toHtmlSpanned in ItemRowView.updateWatched 2022-02-02 16:43:49 +01:00
Niels van Velzen
bfd9b58762 Allow HTML in login disclaimer 2022-02-02 16:43:49 +01:00
mortetm
3e64a120c7 Translated using Weblate (Croatian)
Currently translated at 84.3% (415 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hr/
2022-02-01 17:13:18 -05:00
WWWesten
811afdd034 Translated using Weblate (Russian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-01-31 06:13:18 -05:00
WWWesten
e47de9d892 Translated using Weblate (Kazakh)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-31 06:13:18 -05:00
Bill Thornton
e06e7fa4f7 Merge pull request #1399 from nielsvanvelzen/fix-search-provider 2022-01-30 12:34:47 -05:00
Niels van Velzen
6e5919d35a Fix searchable.xml not supporting ${applicationId} 2022-01-30 17:50:40 +01:00
Bill Thornton
4118ec8649 Merge pull request #1398 from nielsvanvelzen/fix-search-provider 2022-01-30 11:30:43 -05:00
Niels van Velzen
58f290f910 Fix MediaContentProvider authority not using ${applicationId} 2022-01-30 17:17:39 +01:00
Niels van Velzen
9a17b8bbfe Migrate NextUpViewModel to use SDK for images 2022-01-29 11:30:56 +01:00
Niels van Velzen
06e695e654 Migrate BaseItemUtils.buildChapterItems to use SDK for images 2022-01-29 11:30:56 +01:00
Niels van Velzen
c5182b34f8 Migrate BackgroundService to use SDK for images 2022-01-29 11:30:56 +01:00
Niels van Velzen
f8c7ba0e83 Add opus as supported audio codec for ExoPlayer 2022-01-29 10:37:25 +01:00
Niels van Velzen
9bcf6f7d20 Suppress RestrictedApi issues for Leanback library in LeanbackChannelWorker 2022-01-29 10:36:24 +01:00
Niels van Velzen
ea080fed7a Upgrade Android Gradle Plugin to 7.1.0 2022-01-29 10:36:24 +01:00
cameron
799201c670 queue row should position itself reliably now 2022-01-29 10:29:41 +01:00
cameron
e484722312 removed shufflequery, removed shuffle from the artist/album query builder 2022-01-29 10:28:52 +01:00
cameron
70227bdf74 shuffle the audio queue locally 2022-01-29 10:28:52 +01:00
cameron
79457860e2 reset lastUniqueProgressEvent and lastReportedPlaybackPosition in stopProgressLoop 2022-01-29 10:26:31 +01:00
cameron
087d99aad4 temporary handler for device going to sleep while playing, release player on complete 2022-01-29 10:26:31 +01:00
cameron
7cd7de4795 handle audio focus loss, set newplaying bug pos to 0 on idle 2022-01-29 10:26:31 +01:00
cameron
e8fed01aa5 fixed buttons not updating onresume when player is not initialized 2022-01-29 10:26:31 +01:00
newton181
f0256613e6 Translated using Weblate (Spanish (Mexico))
Currently translated at 86.7% (427 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2022-01-28 20:13:16 -05:00
Niels van Velzen
452f4d80f3 Fix ImageHelper returning wrong URL when preferParentThumb is true 2022-01-27 19:25:42 +01:00
Niels van Velzen
09013200b0 Initial ImageUtils SDK migration (#1377)
* Send ApiClient instance to ImageUtils.getPrimaryImageUrl

* Add ImageHelper to create image urls using the SDK

* remove apiclient parameter from ImageUtils functions
2022-01-25 15:10:21 +01:00
Cameron
520c1b4951 fix crash from empty or null browsecommand sent from android client (#1385)
* fix crash when remote controlling and quickly press back on android phone

* replaced try catch with guard

* use utils isempty in guard
2022-01-25 14:32:09 +01:00
Thilo Jaeggi
0de3437e4e Translated using Weblate (German)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2022-01-25 05:13:15 -05:00
Niels van Velzen
ca170a28f8 Add repository condition to stale workflow 2022-01-25 08:36:04 +01:00
K3rnelPan1c
75eb179688 add stale issue check workflow (#1379)
* add stale issue check workflow

this should replace the functionality of the discontinued stale-bot

* change PAT use for stale action

* change to address review comments
2022-01-24 08:56:23 +01:00
Romain Eggermont
a275ad83c0 Translated using Weblate (French)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-01-23 10:29:23 -05:00
Niels van Velzen
014e52d1a8 Fix nullability for UserViewsRepository.isSupported and UserViewsRepository.allowViewSelection 2022-01-23 15:57:34 +01:00
Niels van Velzen
198fd5ae95 Move user view related logic to UserViewsRepository 2022-01-23 08:35:27 +01:00
Niels van Velzen
1c67dff399 Add libraries link to customization preferences
This is a shortcut to open the display preferences for user views
2022-01-23 08:35:27 +01:00
Niels van Velzen
16fc07abc0 Remove unused stuff from MyDetailsOverviewRow 2022-01-22 16:25:55 +01:00
David Fairbrother
7225d0d053 Add check to PlaybackController for LiveTV Case
Since we no longer create a speed controller when viewing live TV
nothing will reset the playback speed.
It's easier (from a bug fixing POV) to know that speed controller is not
involved in live TV at all, even if it adds additional checks to
PlaybackController
2022-01-22 16:01:23 +01:00
David Fairbrother
3c97be93aa Hide speed controls when displaying live TV
Hide the playback speed controls, since this is effectively useless with
the speed being forced to 1x
2022-01-22 16:01:23 +01:00
David Fairbrother
0da44393a0 Cap speed at 1.0 when live TV is on
We could allow various things, like a speed less than 1 or greater than
until we exhaust the buffer.

Unfortunately I don't have the equipment to test more complex
interactions, so lets play it safe and force it always to 1.0
2022-01-22 16:01:23 +01:00
cameron
0bf09f92e0 removed condition that shouldnt have been added 2022-01-22 15:02:49 +01:00
cameron
2bc8fc1df2 dynamically fill button row and hide extra 2022-01-22 14:48:39 +01:00
Niels van Velzen
e1dc58ff16 Remove 1.5 Mbit/s from max streaming bitrate option
The calculated name for it was wrong and removing it was easier then fixing the name.
2022-01-22 14:46:49 +01:00
ValentinChirikov
b7f36b6561 Translated using Weblate (Russian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-01-22 06:13:14 -05:00
nextlooper42
4a56ddab5e Translated using Weblate (Slovak)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-01-21 13:13:14 -05:00
David Fairbrother
4313591525 Replace deprecated SimpleExoPlayer -> ExoPlayer
As per the 2.16.0 release notes:
https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md#2160-2021-11-04

SimpleExoPlayer is deprecated, and all functionality has been moved to
ExoPlayer. This allows us to replace these calls as-is and future proofs
us too.
2022-01-21 17:16:16 +01:00
Cameron
620c806f6a on resuming fulldetails activity give play button focus if resume button isnt eligible (#1371)
* give play button focus if resume button isnt eligible

* removed unnecessary code
2022-01-21 17:14:03 +01:00
Cameron
3aee08b245 patch bug where video queue can be messed up and item progress is incorrect after playback (#1372)
* check if fragment belongs to curr session, release player in controller

* guard against VideoManager being null
2022-01-21 17:13:40 +01:00
nextlooper42
0da05cc17c Translated using Weblate (Slovak)
Currently translated at 89.8% (442 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-01-21 09:13:13 -05:00
Deleted User
f73c2f2efa Translated using Weblate (Swedish)
Currently translated at 85.5% (421 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2022-01-20 10:13:13 -05:00
Cameron
29c035e5c8 more playback leak fixes (#1368)
* fixed null reference crashes and improved handling of playing incompatible item

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java

* fixed leaks in main player

* fixed leaks in live tv

* removed unused overrides, fixed formatting
2022-01-20 13:48:53 +01:00
Cameron
c85751ed31 Bypass hls seeking behavior for devices not using exoplayerprofile (#1362)
* wip workaround for device that rejects hls

* fixed condition in onprepared, removed condition in seek

* fix for live tv and hls, prevent seeking on starting live tv playback
2022-01-19 19:43:08 +01:00
Cameron
6251ed404b fixed crashes on failing to start playback of an unsupported or unavailable video (#1360)
* fixed null reference crashes and improved handling of playing incompatible item

* added some delay before the activity finished

* fixed null reference crashes and improved handling of playing incompatible item

* added some delay before the activity finished

* a couple additional guards against the player being null

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java

* more guards

* fixed activity not finishing on too many errors
2022-01-19 19:31:48 +01:00
David Fairbrother
ccaf86008e Exoplayer: Replace setPlaybackSpeed with setPlaybackParameters
Replace the deprecated / removed method setPlaybackSpeed with the more
modern setPlaybackParameters. This matches the current docs for
exoplayer
2022-01-19 19:09:59 +01:00
Lukáš Kucharczyk
eff21d0fa6 Translated using Weblate (Czech)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-01-19 05:13:13 -05:00
Penguins114
395d384908 Translated using Weblate (Japanese)
Currently translated at 72.3% (356 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ja/
2022-01-18 21:13:13 -05:00
Bill Thornton
55517d12d3 Merge pull request #1363 from nielsvanvelzen/stale-is-stale 2022-01-18 18:55:22 -05:00
Niels van Velzen
4709e6f82d Remove stale.yml 2022-01-18 21:44:07 +01:00
Oatavandi
eafd84c2a1 Translated using Weblate (Tamil)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2022-01-18 13:13:13 -05:00
hoanghuy309
d1bc46529e Translated using Weblate (Vietnamese)
Currently translated at 97.5% (480 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2022-01-18 08:13:13 -05:00
WWWesten
643fda9ed7 Translated using Weblate (Ukrainian)
Currently translated at 31.3% (154 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2022-01-18 08:13:13 -05:00
wolong gl
5f82d12e96 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-01-18 08:13:13 -05:00
Renaldo Eduardo Freire
9d49bad241 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2022-01-18 08:13:13 -05:00
Niels van Velzen
46d2fbe9a5 Remove unused RemoveCurrentItem and RefreshRows CustomMessage types 2022-01-18 10:16:53 +01:00
Niels van Velzen
0c34e7dea4 Remove unused QueryType values (#1356)
* Remove unused ViewType enum

* Remove unused StaticServers and Users QueryTypes

* Remove unused ContinueWatching QueryType

Continue watching in the home screen uses a different type (probably StaticItems)
2022-01-18 10:16:32 +01:00
Alan Azar
a4b6516e86 Translated using Weblate (Arabic)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2022-01-18 01:13:13 -05:00
WWWesten
050945b6a3 Translated using Weblate (Esperanto)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-01-17 16:00:23 -05:00
WWWesten
f2e2996a32 Translated using Weblate (Kazakh)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-17 16:00:23 -05:00
WWWesten
e86c32bcb0 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2022-01-17 16:00:23 -05:00
Niels van Velzen
3809fe6a76 Fix typo "neccesary" > "necessary" 2022-01-17 22:00:18 +01:00
WWWesten
43194095ed Translated using Weblate (Kazakh)
Currently translated at 98.1% (483 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-17 12:13:12 -05:00
Csaba
e2bcecec2b Translated using Weblate (Hungarian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2022-01-17 09:13:15 -05:00
nextlooper42
b72b6bd60d Translated using Weblate (Slovak)
Currently translated at 89.2% (439 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-01-17 09:13:14 -05:00
Dee
7e02b012c7 Translated using Weblate (Chinese (Simplified))
Currently translated at 91.0% (448 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-01-17 09:13:13 -05:00
WWWesten
984df9c613 Translated using Weblate (Kazakh)
Currently translated at 94.1% (463 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-17 09:13:13 -05:00
Alex Camilleri
a35d16372b Translated using Weblate (Italian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2022-01-17 09:13:12 -05:00
cameron
9f7478a7f2 fixed a crash caused by the leak fixes pr 2022-01-17 15:10:27 +01:00
hoanghuy309
91c4629d20 Translated using Weblate (Vietnamese)
Currently translated at 97.5% (480 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2022-01-16 19:13:14 -05:00
WWWesten
8b685aaf8a Translated using Weblate (Russian)
Currently translated at 100.0% (492 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-01-16 19:13:13 -05:00
Niels van Velzen
f1e5f9ebe0 Translated using Weblate (Dutch)
Currently translated at 86.1% (424 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2022-01-16 19:13:13 -05:00
millallo
e67c2da53c Translated using Weblate (Italian)
Currently translated at 98.7% (486 of 492 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2022-01-16 19:13:13 -05:00
Weblate
e5e61107ef Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2022-01-16 10:08:11 -05:00
Niels van Velzen
a9845fd3b2 Use slide right animation for preferences theme 2022-01-16 16:08:07 +01:00
Niels van Velzen
d5f85d8015 Merge lazyOptionsScreen and optionsScreen into a single (non-lazy) getter 2022-01-16 16:08:07 +01:00
Niels van Velzen
21d495fdd2 Remove unused strings
Only in main strings.xml - translations will be removed automatically by Weblate
2022-01-16 16:08:07 +01:00
Niels van Velzen
669893be12 Refactor preferences UI
- Use separate screens for a few main categories
- Categorize preferences
- Reorder preferences/categories/screens
- Rename strings to make more sense
- Add missing descriptions
- Rename "settings" to "preferences"
- Rename "user" to "account"
- Use Normal Capitalization Instead Of Title Casing
- Add icons to preference links and about items
- Remove Utils.getVersionString
2022-01-16 16:08:07 +01:00
Niels van Velzen
5545f29119 Fix bad string formatting in server issue strings 2022-01-16 16:08:07 +01:00
Niels van Velzen
8ed44a6200 Use default width for preferences overlay 2022-01-16 16:08:07 +01:00
siankatabg
72ae1d3e3e Fix broken subtitles line wrapping (#1346)
* Fix broken subtitles line wrapping after my last PR

Apparently I did a mistake using the ReplacementSpan for outlining the text as it doesn't support line wrapping, so lets do it with a custom TextView :)

* Code fixes

* Another one...
2022-01-16 16:07:31 +01:00
Cameron
1e8ea6cb5b use HLS for exoplayer with transcoded streams (#1342)
* WIP use hls for transcoded streams


replaced progressive media source, use mediaitem

moved hls stuff to exoplayer profile, revert some playbackcontroller logic so vlc works


hevc support - needs testing

* edited wip profile adder

* removed wip profile adder

* removed debug output

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

* Update app/src/main/java/org/jellyfin/androidtv/util/profile/ExoPlayerProfile.kt

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

* resolved merge conflict

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2022-01-16 12:18:28 +01:00
Cameron
2364acfc1b fixed some playback leaks (#1350)
* fixed some playback leaks

fixed leaks in caused by report loop in videomanager

clear references to videomanager and overlay fragment in playbackcontroller when playback ends
restored method accidentally deleted

disabled screensaver

handle home button interrupting playback by ending the playback activity

* added back missing activity finish

* missed a few setKeepScreenOn
2022-01-16 11:41:49 +01:00
nextlooper42
3ff1cd142b Translated using Weblate (Slovak)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-01-15 10:13:11 -05:00
Niels van Velzen
c8cf372301 Translated using Weblate (Dutch)
Currently translated at 98.3% (469 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2022-01-15 07:13:11 -05:00
WWWesten
b0052caa18 Translated using Weblate (Kazakh)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-15 04:13:11 -05:00
WWWesten
49139a8ee0 Translated using Weblate (Esperanto)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-01-14 19:13:11 -05:00
Zorglube
73bcf64781 Translated using Weblate (French)
Currently translated at 98.7% (471 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-01-14 11:13:11 -05:00
Renaldo Eduardo Freire
545b9f958f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2022-01-14 04:13:11 -05:00
Theofilos Moupasiridis
1355f36048 Translated using Weblate (Greek)
Currently translated at 90.3% (431 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2022-01-14 00:13:13 -05:00
Nawaf
f4db105c22 Translated using Weblate (Arabic)
Currently translated at 97.9% (467 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2022-01-13 19:13:11 -05:00
WWWesten
d61e3ec607 Translated using Weblate (Esperanto)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-01-12 16:13:11 -05:00
WWWesten
b201be7840 Translated using Weblate (Russian)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-01-12 16:13:10 -05:00
WWWesten
635f4d3542 Translated using Weblate (Kazakh)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-12 16:13:10 -05:00
WWWesten
61a8799c80 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2022-01-12 16:13:10 -05:00
Bill Thornton
e8db923f9c Merge pull request #1343 from nielsvanvelzen/let-me-see-my-shitty-photo-albums-please-you-stupid-app
Fix app crash when opening photo albums
2022-01-12 13:46:28 -05:00
Niels van Velzen
2eb16026b8 Fix app crash when opening photo albums 2022-01-12 19:40:52 +01:00
avicarpio
0ba9530aa6 Translated using Weblate (Catalan)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2022-01-12 13:13:17 -05:00
Dzonkins
67c59a4041 Translated using Weblate (Serbian)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sr/
2022-01-12 13:13:14 -05:00
Csaba
06f9e026b8 Translated using Weblate (Hungarian)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2022-01-12 13:13:14 -05:00
小造xu_zh
a5fac70968 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-01-12 13:13:14 -05:00
Alfonso Scarpino
e4ac9e8624 Translated using Weblate (Italian)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2022-01-12 13:13:14 -05:00
avicarpio
32aa1f60f7 Translated using Weblate (Spanish)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2022-01-12 13:13:13 -05:00
Lukáš Kucharczyk
40043c8b6d Translated using Weblate (Czech)
Currently translated at 100.0% (477 of 477 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-01-12 13:13:10 -05:00
Csaba
6254b8181d Translated using Weblate (Hungarian)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2022-01-12 02:50:10 -05:00
DavidFair
17ba4828a7 Add Playback speed controls (#1302)
* Add basic mocks to unit test PlaybackController

Adds the various basic mocks required to unit test Playback Controller.

At a high level the steps we perform are:
- Prep mocks for deps injected by constructor
- Prep mocks for Koin, then pass these as singletons
- Patch up various calls out to Android SharedPrefs ...etc.

This allows us to construct a PlaybackController in a JVM context now.
These mechanisms will be useful for later in the patch series too....

* Add setPlayback impl and tests to controller/view

Adds setPlaybackSpeed to the view and controller.
The controller has associated tests which built on the earlier ones.

Unfortunately the view also has logic in it and I've tried to (ab)use
mocks to factor out the ExoPlayer factory. However because it's a static
final the mock will always defer to the real impl.

I could introduce some sort of interface and abstract out the calls to
the underlying players using an adapter pattern so we can test this. But
alas, the player rewrite will obselete this and I'm 8+ hours into
getting tests to work...

* Add VideoSpeedController and tests

Adds a new controller for VideoSpeed, to help encapsulate items
following SRP.
This also adds a mechanism to track the users most recent speed
selection, so switching from videos / to other media and back will
restore the user's selection on the next video.

* Switch to using Enums to representing speed

This makes it much easier to compare for equality and has built in
indexing making the menu much easier to work with.

In comparison using doubles directly would have required us to add a
comparison lambda in the view to correctly detect what was selected /
determine what is selected.

* Wire up view for Playback speed controls

Wires up the view component for playback speed controls, based on the
pattern used in various other action items.

* Preserve playback speed between videos

- Update the PlaybackController to internally track the last playback
speed, but still treat it as a black box from all external callers
- Add a test we don't throw a NPE trying to set it before init
- There's no test for checking this value gets forwarded (see below)
- Change the activity to create the speed controller whilst the
ControlGlue is being created.

This is unfortunately a bit of a hack. There isn't a signal for
onPlaybackStarted or similar we can attach to from the playback
controller so we have to mess with the internals. Something I've tried
to avoid greatly.

I've tried adding a unit test with mocks, which was surprisingly going
well. Until we create LibVLC objects mid-way. These needs some sort of
factory pattern to inject mocks else it will query Android for playback
capabilities failing the test.

I'm going to leave this particular test as a manual case as I extracting
factories out with the pending rewrite is a futile effort.

* Fix various nits / warnings from CI

This includes:
- Using magic values in our enum
- Impliclt Locale issues
- Nullability checks

* Port drawable from Exoplayer into drawables

Ports the playback speed drawable into the projects list of drawable
items, following PR feedback.

The license is preserved (Apache 2.0) and compatible with the GPL
license of this project.

* Apply various code improvements to PlaybackSpeedAction

Includes the following from the PR review:
- Whitespace tidy up
- Implicitly return from lambda instead of explicitly naming it
- Use apply to remove a significant amount of boilerplate

* Add check that setPlaybackSpeed is sane

This will ignore any values that are clearly incorrect i.e. <0.25.

This means the playback controller can still have a sentinel value of
-1.0 to ensure the model controlling speed is in-place correctly. Whilst
not breaking user playback by default if something goes wrong.

As this file directly interfaces with the video manager (which holds
surfaces) we can't easily make a test for it, so leave this block
untested

* DI interface instead of concrete type

Inject the interface for shared preferences rather than the concrete
type.
This avoids Mockk trying to grab the abstract class underneath, which
would run the real impl instead. It also brings our DI in this file more
in-line with the commonly used pattern

* Address PR comments

- Revert some noisy automatic formatting
- Move speed steps out of companion object
- Drop supuerfluous interface

* Tidy up VideoSpeedController from PR review

- Use Kotlin a kotlin setter directly
- Make the variable public
- (Small amount of rewiring to make everything work)

* Drop PlaybackController tests and revert DI

Drop PlaybackController test. We can't simply inject by interface as
this isn't type safe with how it's currently implemented.

Since it's likely PlaybackController is going to undergo a major rework
with pending future work, let's simply drop the tests and revert the
associated changes.
2022-01-12 08:50:06 +01:00
hoanghuy309
483ad96f50 Translated using Weblate (Vietnamese)
Currently translated at 98.5% (469 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2022-01-12 01:13:10 -05:00
WWWesten
70a33f2aad Translated using Weblate (Esperanto)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2022-01-11 18:13:12 -05:00
Dzonkins
55329048b3 Translated using Weblate (Serbian)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sr/
2022-01-11 18:13:12 -05:00
nextlooper42
7fe910cb08 Translated using Weblate (Slovak)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-01-11 18:13:11 -05:00
WWWesten
35339ec33f Translated using Weblate (Russian)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2022-01-11 18:13:10 -05:00
WWWesten
1d8aaffcb8 Translated using Weblate (Kazakh)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2022-01-11 18:13:10 -05:00
WWWesten
6ae22bbd33 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2022-01-11 18:13:10 -05:00
Lukáš Kucharczyk
f14dd1f441 Translated using Weblate (Czech)
Currently translated at 100.0% (476 of 476 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-01-11 10:13:10 -05:00
Niels van Velzen
85321d89b9 Show server connection feedback in UI 2022-01-11 13:17:33 +01:00
Niels van Velzen
7bd92d6b19 Collect server issues in ServerRepository.add 2022-01-11 13:17:33 +01:00
Thomas Schwery
a52ccfd515 Translated using Weblate (French)
Currently translated at 99.1% (464 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-01-10 03:13:09 -05:00
nextlooper42
41c37ffd69 Translated using Weblate (Slovak)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2022-01-09 19:13:09 -05:00
Penguins114
3ff4471e7a Translated using Weblate (Japanese)
Currently translated at 84.8% (397 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ja/
2022-01-09 11:13:11 -05:00
Joel Jose
6572ed093a Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2022-01-08 07:13:09 -05:00
Renaldo Eduardo Freire
dab3dfaf42 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2022-01-06 01:13:08 -05:00
Bill Thornton
1f122e862a Merge pull request #1334 from jellyfin/issue-template-typo-1 2022-01-05 23:22:58 -05:00
Cameron
318828ac2e Update bug-report-playback.yaml 2022-01-05 20:17:29 -08:00
Tony
41e43d7b10 Translated using Weblate (Indonesian)
Currently translated at 70.5% (330 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2022-01-05 14:13:08 -05:00
Csaba
e6ba5d890e Translated using Weblate (Hungarian)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2022-01-05 03:13:07 -05:00
cameron
a481410d6e replaced magic number 2022-01-05 08:36:14 +01:00
mueslimaker
9e41b5e648 added logging for discontinuity and timeline change 2022-01-05 08:36:14 +01:00
Thomas Schwery
f4f3f178a5 Translated using Weblate (French)
Currently translated at 98.7% (462 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2022-01-04 05:05:56 -05:00
dependabot[bot]
a5d72c24bd Bump burnett01/rsync-deployments from 5.1 to 5.2
Bumps [burnett01/rsync-deployments](https://github.com/burnett01/rsync-deployments) from 5.1 to 5.2.
- [Release notes](https://github.com/burnett01/rsync-deployments/releases)
- [Commits](https://github.com/burnett01/rsync-deployments/compare/5.1...5.2)

---
updated-dependencies:
- dependency-name: burnett01/rsync-deployments
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 18:07:01 +01:00
hoanghuy309
94a2fd4f38 Translated using Weblate (Vietnamese)
Currently translated at 98.5% (461 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2022-01-03 04:05:56 -05:00
edwardewinter
302e2b6e6e Translated using Weblate (Swedish)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2022-01-03 04:05:56 -05:00
Tim Gaubel
fbc5cc0050 Translated using Weblate (German)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2022-01-02 15:05:56 -05:00
millallo
1d5f827392 Translated using Weblate (Italian)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2022-01-02 12:05:57 -05:00
Im-Mefju
89ee540fde Translated using Weblate (Polish)
Currently translated at 97.2% (455 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2022-01-02 12:05:57 -05:00
Mark Shui Hu
375c47e733 Translated using Weblate (Dutch)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2022-01-02 12:05:56 -05:00
ldoench
cd1a97a49f Translated using Weblate (German)
Currently translated at 99.7% (467 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2022-01-02 12:05:56 -05:00
David Fairbrother
ef29134163 Run formatter over playback controller
Runs the Android studio formatter over the playback controller. This
should make it easier to use automated tooling and clear up some diffs
which have touched this file too.
2022-01-02 15:59:26 +01:00
bestkayle
2cfeeaf6a2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2022-01-01 08:05:55 -05:00
Cameron
8001196183 improved skip behavior - especially with repeated button presses (#1325)
* improved skipping behavior

* renamed property to mSeekPosition
2022-01-01 11:38:38 +01:00
Lukáš Kucharczyk
110a353cf6 Translated using Weblate (Czech)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2022-01-01 05:05:56 -05:00
WWWesten
21dce4be19 Translated using Weblate (Esperanto)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-12-31 11:05:57 -05:00
denisplay
c47e94a2a9 Translated using Weblate (Hebrew)
Currently translated at 87.8% (411 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2021-12-31 11:05:56 -05:00
WWWesten
5b55ca7eb9 Translated using Weblate (Russian)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-12-31 11:05:56 -05:00
WWWesten
15c14d92ca Translated using Weblate (Kazakh)
Currently translated at 100.0% (468 of 468 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-12-31 11:05:55 -05:00
Cameron
93b820758f reset player error count if playback stabilizes (#1324)
* reset player error count if stable

* moved retry count reset to more sensible location
2021-12-31 11:46:36 +01:00
WWWesten
cf2cef35f6 Translated using Weblate (Arabic)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-12-31 05:22:19 -05:00
Cameron
05329ff83e allow setting the nextup timer duration to infinite (#1274)
* replaced 0s with infinity character, nextup timer doesnt start if timeout is 0s/infinity

* updated code to reflect actual range of 0 to 30000

* moved preferences outside view, updated pref values

* removed unused variable

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpButtons.kt

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpButtons.kt

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

* made label an R string, use constant for timer value, inject pref

* val -> const val

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpFragment.kt

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2021-12-31 11:22:15 +01:00
Niels van Velzen
3e862b8b82 Translated using Weblate (Arabic)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-12-30 16:45:17 -05:00
Bill Thornton
07e535ead0 Merge pull request #1323 from nielsvanvelzen/slf4j-timber
Replace slf4j-android with slf4j-timber
2021-12-30 13:30:35 -05:00
Niels van Velzen
13b8ce2c5b Replace slf4j-android with slf4j-timber 2021-12-30 19:16:10 +01:00
DavidFair
926a2b7a4b Add Mockk and one test to exercise the new dep (#1312)
* Add Mockk to test framework deps

Add Mockk as this is aware of Kotlin inheritence unlike Mockito.
This primarily shows up where classes are not marked open. We can
either:
- Mark all unit tested classes open (least viable option)
- Add boilerplate to Mockito each time we test a Kotlin class
- Add a mocking framework that is Kotlin aware.

In this case I've opted for the latter, since it also avoids us having
to look up the boilerplate for static mocks...etc.

* Stub out testing Android.utils.log

Slf4j will try to invoke IsLoggable, despite stubbing out everything
else, which is explicitly disabled within the Android SDK.

We could revert to hacks, such as using Android -> returnDefaultValue=true in the
testing options. But according to the docs this should be a last resort.

Instead we can simply stub out the logging to forward calls to println,
allowing us to mock / init anything that uses SLF4J implicitly.

* Add tests and null checks for DeviceUtils

This commit does two things to help testing going forwards:

- It adds a null check for Build.MODEL, typically found in unit tests.
Allowing us to simply avoid having to mock all the various capability
tests which were throwing NPEs in unit tests.

- Adds tests for the existing functionality. This requires Mockk to
easily overwrite static class methods. We also add a small shim method
to make mocking much easier too.

* Run CustomSeekProvider through automatic Java->Kotlin

Convert the file to Kotlin. This means all of our unit tests that use
Mockk will have the same syntax inside, making it much easier for future
developers to reason with.

* Convert Mockito to Mockk

Converts the single usage of Mockito to Mockk, so we can use the same
mocking framework throughout the codebase.

* Drop Mockito in favour of Mockk

Drops Mockito to use Mockk throughout the project.
This is because we have a mix between Java and Kotlin. The latter is
"harder" to mock as classes are not marked open by default.

This means Mockito requires boilerplate in comparison to Mockk.
Additionally, using a single framework lowers the burden on developers
to known multiple tools

* Run Android Studio formatter over code

Despite having format on save enabled, many lines were missed (probably
from the cherry-pick).
Run formatter over the code to address PR reviewer comments.

* Return string instead of null from getBuildModel

This removes a number of null checks from the implementation, and allows
us to mark the method as nonNull too.
2021-12-30 18:44:21 +01:00
Cameron
c38570186b Fix irregular playback overlay showing and hiding (#1321)
* overlay show/hide fixes

* replaced use of setFadingEnabled in hide method to prevent calling show again
2021-12-30 14:01:24 +01:00
Cameron
e68ed8f738 made fullDetails button refresh after playback more reliable (#1320)
* made button updates after playback more reliable

* removed redundant condition
2021-12-30 13:59:54 +01:00
wolong gl
6f39548eaf Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2021-12-30 06:05:55 -05:00
millallo
3db66ac834 Translated using Weblate (Italian)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-12-30 06:05:55 -05:00
4d1m
a2697e8c8c Translated using Weblate (Romanian)
Currently translated at 89.7% (419 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2021-12-29 20:05:54 -05:00
Fahad
4c5eb0d02a Translated using Weblate (Arabic)
Currently translated at 99.7% (466 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-12-29 02:05:54 -05:00
WWWesten
ff2fca0d81 Translated using Weblate (Esperanto)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-12-28 17:05:55 -05:00
WWWesten
ca028be237 Translated using Weblate (Russian)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-12-28 17:05:54 -05:00
WWWesten
bd576dfda1 Translated using Weblate (Kazakh)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-12-28 17:05:54 -05:00
Lukáš Kucharczyk
bd7313f05d Translated using Weblate (Czech)
Currently translated at 100.0% (467 of 467 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2021-12-28 17:05:54 -05:00
siankatabg
eadf7b93df Add subtitles font size and background configurations (#1303)
* Add subtitles font size and background configurations

Added seekbar for choosing subtitles font size (18-38 range, keeping 28 by default)
Added text outlining for better legibility and an option to disable the subtitles black background.

* Fixes
2021-12-28 18:07:36 +00:00
David Fairbrother
480a71d1a6 Emit JUnit output whilst running gradle through CLI
Tweaks the gradle script to forward on test failures in a way that the
stack trace is preserved.

Previously, the stack pointed to a line after the test suite, e.g. line
101 on an 80 line file.
2021-12-28 18:54:17 +01:00
Bill Thornton
c7d8fe09a0 Merge pull request #1316 from nielsvanvelzen/sdk-112
Update SDK to 1.1.2
2021-12-27 11:51:06 -05:00
Niels van Velzen
be9890f5ab Update SDK to 1.1.2
Fixes an crash when using display preferences
2021-12-27 16:34:57 +01:00
Cristian
106039bedc Translated using Weblate (Spanish)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-12-26 10:05:53 -05:00
cameron
6a297ce7dc signal listeners only after playback starts, replace runnables 2021-12-26 11:27:02 +01:00
cameron
7475d196da stop audio before queue is cleared to prevent bad reference 2021-12-26 11:23:26 +01:00
WWWesten
0e892eed1e Translated using Weblate (Kazakh)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-12-25 16:05:53 -05:00
David Fairbrother
2990ffad09 Add Koin unit testing deps implementation
This allows Koin to do DI during unit tests and allows us to establish
Junit rules which involve Koin
This is essential for testing some classes as the playback code, where
DI is used heavily
2021-12-25 12:17:24 +01:00
mueslimaker
6951a7fe26 fixed nowplaying scrolling on queue row gaining focus 2021-12-23 13:59:59 +01:00
gradle-update-robot
8113f01777 Update Gradle Wrapper from 7.3.2 to 7.3.3.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2021-12-22 14:46:59 +01:00
WWWesten
c6850df658 Translated using Weblate (Welsh)
Currently translated at 33.8% (157 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cy/
2021-12-21 11:05:56 -05:00
WWWesten
be2935ee5d Translated using Weblate (Turkish)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2021-12-21 11:05:52 -05:00
WWWesten
8014be1c55 Translated using Weblate (Russian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-12-21 11:05:52 -05:00
Thomas Schwery
798b498417 Translated using Weblate (French)
Currently translated at 98.7% (458 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2021-12-21 11:05:52 -05:00
WWWesten
ea4d88bdb4 Translated using Weblate (English (United Kingdom))
Currently translated at 96.9% (450 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2021-12-21 11:05:52 -05:00
WWWesten
b21a222f1e Added translation using Weblate (Estonian) 2021-12-21 04:54:37 -05:00
WWWesten
df27e8b937 Added translation using Weblate (Welsh) 2021-12-21 04:52:45 -05:00
Georgios Stamos
a22bdc185b Translated using Weblate (Bulgarian)
Currently translated at 19.1% (89 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bg/
2021-12-20 22:05:53 -05:00
Niels van Velzen
59150dc811 Update all dependencies 2021-12-20 09:33:42 +01:00
Niels van Velzen
cac209f47f Always use version references in version catalog
This makes it easier to update the versions and makes the file more readable
2021-12-20 09:33:42 +01:00
Niels van Velzen
a8564c53aa Target API 31 2021-12-20 09:33:42 +01:00
LowHigh
6be7f34e63 Translated using Weblate (French)
Currently translated at 95.4% (443 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2021-12-19 21:05:51 -05:00
Niels van Velzen
ced03b82de Move Gradle plugins to plugin catalog (#1296)
* Move Gradle plugins to plugin catalog

* Fix linter config for playback module
2021-12-19 14:44:28 +01:00
mueslimak3r
20138f2d38 fixed a regression in player prepare 2021-12-19 12:22:12 +01:00
Cody Robibero
a66ea7fa79 Merge pull request #1295 from nielsvanvelzen/release-aab 2021-12-18 16:36:48 -07:00
Cameron
defc7af702 prevent unwanted interactions when music player screensaver active (#1290)
* prevent unwanted interactions with screensaver active

* Update app/src/main/java/org/jellyfin/androidtv/util/KeyProcessor.java

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

* changed popup menu name and removed comment

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2021-12-19 00:10:31 +01:00
Niels van Velzen
8005531831 Also build app bundle for Google Play in CI 2021-12-18 22:54:10 +01:00
mueslimaker
07d8de22cc if an audio queue exists show add to queue instead of play in itemlistactivity 2021-12-18 22:45:02 +01:00
Oatavandi
9af1e232b4 Translated using Weblate (Tamil)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2021-12-17 13:05:50 -05:00
gradle-update-robot
e3c471e141 Update Gradle Wrapper from 7.3.1 to 7.3.2.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2021-12-16 08:52:15 +01:00
mueslimaker
1771f28012 removed condition that always reports !canSeek when using fire tv 2021-12-15 22:53:23 +01:00
mueslimaker
c7fbb1b3ef bring firetv controls in line with the new player controls 2021-12-15 22:53:23 +01:00
mueslimaker
1741795c89 fixed add artist to queue, added shuffle artist 2021-12-15 08:52:51 +01:00
mueslimaker
85024c7563 audio play from doesnt discard the preceding items in its parent 2021-12-15 08:50:48 +01:00
Cameron
dd368cc703 Fixed some remote play commands (#1273)
* fixed up mediaManager queue and audio reporting

* fixed remote playback commands pt 1/2

* audio pause reports to server sooner now

* separate check for audio focus and player init, stopAudio optionally releases player

* immediate pause reporting, slowed down report loop when paused

* resumeAudio calls reportprogress with paused=false instead of reportStarted

* more strict checks before remote videoplayer interaction, can remote start audio from specific queue index

* missed a curly bracket in the merge conflict resolve tool

* updated comment

* made mainThreadHandler a class property

* mediamanager remakes input items list on ingest to ensure type

also patched an issue where nextup would load the same episode again when playing via remote control

* removed "this"

* fixed div by 0, remote stop discarding progress, remote resume from

* replaced new Long() with Long.valueOf()
2021-12-14 09:07:37 +01:00
Niels van Velzen
61a96d7eea Merge ByLetterActivity and ByGenreActivity into a single activity (#1222)
* Merge ByLetterActivity, BrowsePersonsActivity and ByGenreActivity into a single activity

* Remove unused code from BrowseFolderFragment

* Remove unused BrowsePersonsFragment

* Merge CustomViewFragment into BrowseFolderFragment

* Fix wrong constant used

* Remove unused code from StdBrowseFragment (activity is never BaseActivity)
2021-12-13 16:08:18 +01:00
caesartcs
e059a51032 Translated using Weblate (Romanian)
Currently translated at 89.6% (416 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2021-12-09 20:05:47 -05:00
WWWesten
f6d023a305 Translated using Weblate (Russian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-12-09 07:05:47 -05:00
Cameron
b762f9c48d new in-place, non-destructive shuffle system (#1276)
* shuffle works now

* fixed autocomplete typo - whoops

* updated comment to remove obsolete details

* removed another obsolete piece of logic - no change in behavior but makes more sense

* removed the need to store a copy of the queue. Now it only saves the indexes
2021-12-08 21:27:33 +01:00
Revertron
11c024ec3c Translated using Weblate (Russian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-12-08 15:05:47 -05:00
Cameron
da609a4574 reworked playbackController position tracking and reporting system (#1261)
* reworked position tracking and reporting system

* adjusted directplay seeking and removed debug printout

* removed use of mSeekedPosition from directplay seeking

* fixed typo in my comment

* formatting fixes
2021-12-08 20:57:34 +01:00
WWWesten
0310dc4863 Translated using Weblate (English (United Kingdom))
Currently translated at 87.9% (408 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2021-12-08 05:05:47 -05:00
newton181
3dca3fd5aa Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2021-12-07 17:05:46 -05:00
mueslimaker
f52f10b720 fix - removed method argument used in testing branch 2021-12-05 15:26:49 +01:00
mueslimaker
303767f747 fixed crash when playing audio and going from first to last item in repeat mode
-1 index out of bounds on managed audio queue
2021-12-05 15:26:49 +01:00
RTapeLoadingError
27645414e8 Translated using Weblate (Spanish)
Currently translated at 99.5% (462 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-12-04 21:05:45 -05:00
danieladov
060d487004 Translated using Weblate (Spanish)
Currently translated at 99.3% (461 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-12-04 18:05:45 -05:00
Weblate
7f64d4405d Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2021-12-04 03:05:47 -05:00
Weblate
88d582c788 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2021-12-04 03:05:46 -05:00
hoanghuy309
6d99a93218 Translated using Weblate (Vietnamese)
Currently translated at 98.7% (458 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-12-04 03:05:45 -05:00
gradle-update-robot
c33312ca29 Update Gradle Wrapper from 7.3 to 7.3.1.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2021-12-02 08:35:01 +01:00
Diego Massanti
1f5b3c3c88 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2021-11-30 23:05:44 -05:00
Niels van Velzen
defe097047 Add MigrationContext for improved preference migration behavior 2021-11-30 18:33:45 +01:00
hoanghuy309
8f1e3b7b0a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-11-30 04:05:43 -05:00
WWWesten
36b86405b6 Translated using Weblate (Arabic)
Currently translated at 96.5% (448 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-11-27 08:05:42 -05:00
WWWesten
72055485bd Translated using Weblate (Mongolian)
Currently translated at 34.6% (161 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mn/
2021-11-27 08:05:42 -05:00
WWWesten
cbf33dd1c6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.0% (441 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2021-11-27 08:05:42 -05:00
WWWesten
26f97bf531 Translated using Weblate (Vietnamese)
Currently translated at 99.7% (463 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-11-26 20:05:43 -05:00
Weevild
463a5615f4 Translated using Weblate (Swedish)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2021-11-26 20:05:43 -05:00
WWWesten
5bfb277d26 Translated using Weblate (Turkish)
Currently translated at 99.7% (463 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2021-11-26 09:05:42 -05:00
WWWesten
62e255e18a Translated using Weblate (Polish)
Currently translated at 94.3% (438 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2021-11-26 09:05:42 -05:00
WWWesten
b6522a4ba2 Translated using Weblate (Serbian)
Currently translated at 84.0% (390 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sr/
2021-11-26 09:05:42 -05:00
WWWesten
35b1bb1a76 Translated using Weblate (Slovenian)
Currently translated at 73.0% (339 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sl/
2021-11-26 09:05:42 -05:00
WWWesten
858a539596 Translated using Weblate (Italian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-11-26 09:05:42 -05:00
WWWesten
737f3e04b0 Translated using Weblate (German)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2021-11-26 09:05:42 -05:00
WWWesten
8123312b5d Translated using Weblate (Esperanto)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-11-26 05:05:42 -05:00
WWWesten
1e10644c4c Translated using Weblate (Turkish)
Currently translated at 99.7% (463 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2021-11-26 05:05:42 -05:00
WWWesten
d99fc93214 Translated using Weblate (Russian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-11-26 05:05:42 -05:00
WWWesten
aa68c526ff Translated using Weblate (Kazakh)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-11-26 05:05:42 -05:00
ahmet-dv
ccf6e25018 Translated using Weblate (Turkish)
Currently translated at 99.7% (463 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2021-11-25 22:05:44 -05:00
svenjochems
2629e7fe74 Translated using Weblate (Dutch)
Currently translated at 98.2% (456 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2021-11-22 06:05:41 -05:00
Moritz
bf03d2cf19 Translated using Weblate (German)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2021-11-22 06:05:41 -05:00
wolong gl
7a083189b9 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2021-11-22 02:05:40 -05:00
Cameron
267fc2ab16 Fix now-playing row not updating when queue changes (#1258)
* row now updates

* update logic is now self contained

* use replace instead of remove and add

* nowPlayingRow checks hasAudioQueueItems instead of isPlayingAudio

* fixed audio queue not clearing on last song completing

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/MediaManager.java

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2021-11-21 12:40:34 +00:00
EduGaga
d11ead465d Translated using Weblate (Spanish)
Currently translated at 97.8% (454 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-11-20 19:05:40 -05:00
hoanghuy309
23556ab04d Translated using Weblate (Vietnamese)
Currently translated at 99.7% (463 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-11-20 13:05:40 -05:00
Alfonso Scarpino
f707dc1ede Translated using Weblate (Italian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-11-20 08:05:40 -05:00
nextlooper42
023d61a733 Translated using Weblate (Slovak)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2021-11-19 22:05:39 -05:00
mueslimaker
102bcef926 keep exo pos reporting accurate when not playing by using saved pos 2021-11-19 19:45:03 +01:00
WWWesten
80ad473dd6 Translated using Weblate (Esperanto)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-11-19 07:05:40 -05:00
Csaba
f4cafb59de Translated using Weblate (Hungarian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-11-19 07:05:40 -05:00
WWWesten
053f7acc47 Translated using Weblate (Russian)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-11-19 07:05:40 -05:00
WWWesten
cdf3c19195 Translated using Weblate (Kazakh)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-11-19 07:05:39 -05:00
Lukáš Kucharczyk
1e07e726db Translated using Weblate (Czech)
Currently translated at 100.0% (464 of 464 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2021-11-19 07:05:39 -05:00
mueslimaker
916287a142 libvlc playback should now report correct time 2021-11-19 08:58:57 +01:00
hoanghuy309
93d249894d Translated using Weblate (Vietnamese)
Currently translated at 99.1% (458 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-11-19 00:28:52 -05:00
Bill Thornton
ef1a3b830a Merge pull request #900 from koying/addglobalsearch
Implement global search
2021-11-19 00:28:48 -05:00
Cameron
66469dae26 prevent double pausing, allow d-pad center to unpause with overlay hidden (#1252)
* prevent double pausing, allow d-pad center to unpause with overlay hidden

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2021-11-18 19:11:45 +01:00
WWWesten
d282826a0f Translated using Weblate (Mongolian)
Currently translated at 34.8% (161 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mn/
2021-11-18 07:05:39 -05:00
nextlooper42
5610b3fe1e Translated using Weblate (Slovak)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2021-11-17 19:05:39 -05:00
Alfonso Scarpino
457f32dcda Translated using Weblate (Italian)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-11-17 16:05:39 -05:00
mueslimaker
3ef4ad2497 reset exoplay position and state on media change 2021-11-17 20:22:09 +01:00
Chris "koying" Browet
eb5b49abb1 ADD: global search 2021-11-17 20:10:19 +01:00
Csaba
cf04edc93c Translated using Weblate (Hungarian)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-11-17 11:05:40 -05:00
WWWesten
d43c8eca33 Translated using Weblate (Mongolian)
Currently translated at 34.8% (161 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mn/
2021-11-17 11:05:39 -05:00
Lukáš Kucharczyk
e5480205e1 Translated using Weblate (Czech)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2021-11-17 11:05:39 -05:00
WWWesten
2385dc75dc Translated using Weblate (Esperanto)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-11-17 05:05:40 -05:00
WWWesten
e9f6768080 Translated using Weblate (Russian)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-11-17 05:05:39 -05:00
WWWesten
540ec5e754 Translated using Weblate (Kazakh)
Currently translated at 100.0% (462 of 462 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-11-17 05:05:39 -05:00
Bill Thornton
c995856458 Merge pull request #1213 from nielsvanvelzen/home-options 2021-11-16 17:16:06 -05:00
wolong gl
059e0e40ff Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2021-11-12 22:05:37 -05:00
Niels van Velzen
427cc2bb6d Use default transition in PhotoPlayer
also removes some unused code
2021-11-12 20:07:22 +01:00
gradle-update-robot
c9cedd26a6 Update Gradle Wrapper from 7.2 to 7.3.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2021-11-10 13:10:51 +01:00
Csaba
ca1e681d14 Translated using Weblate (Hungarian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-11-10 04:05:36 -05:00
Jay Harkess
0f44915452 Translated using Weblate (English (United Kingdom))
Currently translated at 90.6% (408 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2021-11-09 11:05:37 -05:00
WWWesten
0efedee5b5 Translated using Weblate (Russian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-11-09 10:41:19 -05:00
bahaaza
5a22662700 Translated using Weblate (Arabic)
Currently translated at 99.5% (448 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-11-08 22:13:38 -05:00
Niels van Velzen
204663f71f Delay activity lifecycle callback registry slightly to avoid crashes on older platform versions 2021-11-06 21:33:26 +01:00
Niels van Velzen
f5ecbd08ca Move session initialization to androidx.startup 2021-11-06 21:33:26 +01:00
Niels van Velzen
8ad6eb7cb6 Move logging initialization to androidx.startup 2021-11-06 21:33:26 +01:00
Niels van Velzen
79afee7ce6 Move Koin initialization to androidx.startup 2021-11-06 21:33:26 +01:00
Niels van Velzen
afa93d6ab0 Fix race condition in loading async preferences 2021-11-06 21:33:04 +01:00
Alfonso Scarpino
263afb079d Translated using Weblate (Italian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-11-05 09:05:35 -04:00
Niels van Velzen
a6f17e8903 Fix touch support in home/auth screens (#1230)
* Fix touch support for "switch user" button

* Fix touch support for server button
2021-11-04 15:09:19 +01:00
Jellifi007
faf76efdbd H264 52 Level available on NVIDIA Shield but not detected (#1237) 2021-11-04 14:11:42 +01:00
nextlooper42
ec4cf1601b Translated using Weblate (Slovak)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2021-11-02 21:05:34 -04:00
Pedro Almeida
52b4fa5a7f Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_PT/
2021-11-01 15:05:34 -04:00
WWWesten
574268857b Translated using Weblate (Esperanto)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-31 10:05:33 -04:00
WWWesten
d602080a30 Translated using Weblate (Malayalam)
Currently translated at 91.1% (410 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ml/
2021-10-29 09:05:36 -04:00
WWWesten
74bbffe110 Translated using Weblate (Catalan)
Currently translated at 78.4% (353 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2021-10-29 09:05:36 -04:00
WWWesten
a6709cfac8 Translated using Weblate (Turkish)
Currently translated at 76.0% (342 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2021-10-29 09:05:35 -04:00
WWWesten
2e7fb821cf Translated using Weblate (Hebrew)
Currently translated at 86.0% (387 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2021-10-29 09:05:35 -04:00
WWWesten
aaf43d4c0d Translated using Weblate (Croatian)
Currently translated at 96.2% (433 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hr/
2021-10-29 09:05:35 -04:00
WWWesten
bc9a275bed Translated using Weblate (Spanish (Argentina))
Currently translated at 95.1% (428 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2021-10-29 09:05:35 -04:00
WWWesten
a53ae38276 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 20.2% (91 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bn_BD/
2021-10-29 09:05:35 -04:00
WWWesten
664ba0aba4 Translated using Weblate (Ukrainian)
Currently translated at 38.6% (174 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2021-10-29 09:05:35 -04:00
WWWesten
4331a771f1 Translated using Weblate (Finnish)
Currently translated at 45.5% (205 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2021-10-29 09:05:35 -04:00
WWWesten
77b3c214b2 Translated using Weblate (Lithuanian)
Currently translated at 10.2% (46 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/lt/
2021-10-29 09:05:34 -04:00
WWWesten
9b3d595066 Translated using Weblate (Latvian)
Currently translated at 65.5% (295 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/lv/
2021-10-29 09:05:34 -04:00
WWWesten
628d6662cc Translated using Weblate (Macedonian)
Currently translated at 20.0% (90 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mk/
2021-10-29 09:05:34 -04:00
WWWesten
d972bd3cf1 Translated using Weblate (Polish)
Currently translated at 97.3% (438 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2021-10-29 09:05:34 -04:00
WWWesten
e0f1b15453 Translated using Weblate (Serbian)
Currently translated at 86.6% (390 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sr/
2021-10-29 09:05:34 -04:00
WWWesten
1e121f8891 Translated using Weblate (Slovenian)
Currently translated at 75.3% (339 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sl/
2021-10-29 09:05:34 -04:00
WWWesten
e1be2d5847 Translated using Weblate (Indonesian)
Currently translated at 65.7% (296 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2021-10-29 09:05:34 -04:00
WWWesten
1b0d3f9a6a Translated using Weblate (Slovak)
Currently translated at 97.5% (439 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2021-10-29 09:05:33 -04:00
WWWesten
b949380148 Translated using Weblate (Japanese)
Currently translated at 55.7% (251 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ja/
2021-10-29 09:05:33 -04:00
WWWesten
23a72aa020 Translated using Weblate (Korean)
Currently translated at 74.2% (334 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ko/
2021-10-29 09:05:33 -04:00
WWWesten
0bba0125ea Translated using Weblate (Chinese (Traditional))
Currently translated at 84.6% (381 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hant/
2021-10-29 09:05:33 -04:00
WWWesten
19cdc1e9c9 Translated using Weblate (Romanian)
Currently translated at 86.6% (390 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2021-10-29 09:05:33 -04:00
WWWesten
b6b7c4a0ec Translated using Weblate (Portuguese (Portugal))
Currently translated at 86.6% (390 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_PT/
2021-10-29 09:05:33 -04:00
WWWesten
f98552eb28 Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.2% (424 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nb_NO/
2021-10-29 09:05:32 -04:00
WWWesten
76965b4027 Translated using Weblate (Italian)
Currently translated at 95.5% (430 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-10-29 09:05:32 -04:00
WWWesten
7ba3a06347 Translated using Weblate (English (United Kingdom))
Currently translated at 87.3% (393 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2021-10-29 09:05:32 -04:00
WWWesten
5b13228a68 Translated using Weblate (Greek)
Currently translated at 69.5% (313 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2021-10-29 09:05:32 -04:00
WWWesten
776f532103 Translated using Weblate (Danish)
Currently translated at 87.3% (393 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2021-10-29 09:05:32 -04:00
Alan Azar
8f4fbeeafc Translated using Weblate (Arabic)
Currently translated at 98.4% (443 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-10-29 03:05:32 -04:00
WWWesten
341c53c0d9 Translated using Weblate (Esperanto)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-28 08:05:32 -04:00
WWWesten
0b242a7f1d Translated using Weblate (Russian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-28 08:05:31 -04:00
WWWesten
5845f9597f Translated using Weblate (Esperanto)
Currently translated at 93.5% (421 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-27 19:05:32 -04:00
WWWesten
b1a5fbfc4f Translated using Weblate (Russian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-27 19:05:31 -04:00
WWWesten
61b7550c59 Translated using Weblate (Esperanto)
Currently translated at 84.0% (378 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-27 16:05:32 -04:00
Alan Azar
23028ca72b Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.4% (425 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nb_NO/
2021-10-27 10:05:38 -04:00
Alan Azar
90024accd8 Translated using Weblate (Hebrew)
Currently translated at 85.5% (385 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2021-10-27 10:05:38 -04:00
Alan Azar
33f8dafbe7 Translated using Weblate (Turkish)
Currently translated at 76.2% (343 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2021-10-27 10:05:38 -04:00
Alan Azar
8a218de4a0 Translated using Weblate (Finnish)
Currently translated at 45.7% (206 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2021-10-27 10:05:38 -04:00
Alan Azar
d9e30ac378 Translated using Weblate (Japanese)
Currently translated at 56.0% (252 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ja/
2021-10-27 10:05:37 -04:00
Alan Azar
13cbcc9155 Translated using Weblate (Indonesian)
Currently translated at 66.0% (297 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2021-10-27 10:05:37 -04:00
Alan Azar
1e920acb11 Translated using Weblate (Macedonian)
Currently translated at 20.2% (91 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mk/
2021-10-27 10:05:37 -04:00
Alan Azar
bbbeb777c9 Translated using Weblate (Greek)
Currently translated at 69.5% (313 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2021-10-27 10:05:37 -04:00
Alan Azar
717c26e551 Translated using Weblate (Chinese (Traditional))
Currently translated at 84.8% (382 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hant/
2021-10-27 10:05:37 -04:00
Alan Azar
dcc018033a Translated using Weblate (Mongolian)
Currently translated at 35.3% (159 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mn/
2021-10-27 10:05:37 -04:00
Andreas
6eff855bcc Translated using Weblate (German)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2021-10-27 10:05:36 -04:00
Alan Azar
3ee701a615 Translated using Weblate (Latvian)
Currently translated at 65.7% (296 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/lv/
2021-10-27 10:05:36 -04:00
Alan Azar
64b7a56cd0 Translated using Weblate (English (United Kingdom))
Currently translated at 87.3% (393 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2021-10-27 10:05:36 -04:00
Alan Azar
074fa61bd0 Translated using Weblate (Croatian)
Currently translated at 96.4% (434 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hr/
2021-10-27 10:05:36 -04:00
Alan Azar
22be739d0b Translated using Weblate (Korean)
Currently translated at 74.4% (335 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ko/
2021-10-27 10:05:36 -04:00
Alan Azar
5bef216608 Translated using Weblate (Danish)
Currently translated at 87.3% (393 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2021-10-27 10:05:35 -04:00
Alan Azar
f9e4a0ac8d Translated using Weblate (Italian)
Currently translated at 95.7% (431 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2021-10-27 10:05:35 -04:00
Alan Azar
fc645e5ad5 Translated using Weblate (Portuguese (Portugal))
Currently translated at 86.8% (391 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_PT/
2021-10-27 10:05:35 -04:00
WWWesten
3c015489b2 Translated using Weblate (Russian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-27 10:05:35 -04:00
Alan Azar
d25f91bc49 Translated using Weblate (Slovenian)
Currently translated at 75.5% (340 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sl/
2021-10-27 10:05:34 -04:00
Alan Azar
c7692a64dd Translated using Weblate (Spanish (Argentina))
Currently translated at 95.1% (428 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2021-10-27 10:05:34 -04:00
Alan Azar
bf765e36a1 Translated using Weblate (Slovak)
Currently translated at 97.5% (439 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2021-10-27 10:05:34 -04:00
Alan Azar
a25066a032 Translated using Weblate (Serbian)
Currently translated at 86.8% (391 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sr/
2021-10-27 10:05:34 -04:00
Alan Azar
f540cc2deb Translated using Weblate (Polish)
Currently translated at 97.5% (439 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2021-10-27 10:05:34 -04:00
Alan Azar
db4b312e57 Translated using Weblate (Romanian)
Currently translated at 86.8% (391 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2021-10-27 10:05:34 -04:00
WWWesten
3b70d734b7 Translated using Weblate (Esperanto)
Currently translated at 78.0% (351 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-27 10:05:33 -04:00
Alan Azar
2f163f481a Translated using Weblate (Catalan)
Currently translated at 78.2% (352 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2021-10-26 15:05:32 -04:00
Alan Azar
43644aa9b7 Translated using Weblate (French)
Currently translated at 98.0% (441 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2021-10-26 15:05:32 -04:00
Alan Azar
6484e247d6 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 20.2% (91 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bn_BD/
2021-10-26 15:05:32 -04:00
Alan Azar
4b5efa75e0 Translated using Weblate (Hungarian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-10-26 15:05:32 -04:00
Oatavandi
127ef380e8 Translated using Weblate (Tamil)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2021-10-26 15:05:32 -04:00
Alan Azar
01cc2ca3b3 Translated using Weblate (Arabic)
Currently translated at 98.4% (443 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-10-26 15:05:31 -04:00
Lukáš Kucharczyk
65a11a7378 Translated using Weblate (Czech)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2021-10-25 17:05:32 -04:00
WWWesten
3449d0ab8e Translated using Weblate (Esperanto)
Currently translated at 51.5% (232 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-25 17:05:32 -04:00
WWWesten
716e313da8 Translated using Weblate (Kazakh)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-10-25 07:05:31 -04:00
WWWesten
08d44adcab Translated using Weblate (Esperanto)
Currently translated at 31.5% (142 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-25 07:05:31 -04:00
WWWesten
0e4ad3b410 Translated using Weblate (Russian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-25 07:05:30 -04:00
WWWesten
32131f2f2e Translated using Weblate (Esperanto)
Currently translated at 21.5% (97 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/eo/
2021-10-24 17:05:32 -04:00
WWWesten
786b14f4c1 Added translation using Weblate (Esperanto) 2021-10-24 14:16:05 -04:00
Bill Thornton
5114834210 Merge pull request #1228 from nielsvanvelzen/refresh-server-after-migrate 2021-10-24 11:34:31 -04:00
Niels van Velzen
3ea6bf1293 Refresh server infor after migration 2021-10-24 13:45:09 +02:00
Cameron
3025ed115f fixed playback controls and seeking causing erroneous timestamp updates (#1219)
* removed calls to skip when dpad left/right is called

the new player scrubs seekbar when these dpad buttons are called. Continuing to call seek appears to cause issues with the timestamps

* update playback controller position with seek pos

* dpad no longer calls skip. seek updates current pos

the new player scrubs seekbar when these dpad buttons are called. Continuing to call seek appears to cause issues with the timestamps

update playback controller position with seek pos

* mCurrentPosition is now updated on seeking in direct play

* added changes suggested by nielsvanvelzen
2021-10-24 10:20:33 +02:00
hoanghuy309
4d8244319a Translated using Weblate (Vietnamese)
Currently translated at 99.7% (449 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-10-23 07:05:30 -04:00
Niels van Velzen
453403376a Add preferences for home sections 2021-10-22 22:04:29 +02:00
Niels van Velzen
3cb5f2068d Fix "screen saver" not showing in AudioNowPlayingActivity 2021-10-22 19:23:43 +02:00
Csaba
f4cd8275bc Translated using Weblate (Hungarian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-10-22 07:05:30 -04:00
WWWesten
6409e34244 Translated using Weblate (Kazakh)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-10-21 20:05:31 -04:00
newton181
151d6b3f7d Translated using Weblate (Spanish)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-10-21 20:05:31 -04:00
newton181
a1e3a33c28 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2021-10-21 20:05:30 -04:00
WWWesten
225ab16182 Translated using Weblate (Russian)
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-21 20:05:30 -04:00
newton181
bc63337f15 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (450 of 450 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2021-10-21 20:05:30 -04:00
Bill Thornton
7f72ceb6b4 Merge pull request #1216 from nielsvanvelzen/photalbumcrash
Fix app crash when opening photo album
2021-10-21 13:42:18 -04:00
Niels van Velzen
b98b33d0d2 Fix app crash when opening photo album 2021-10-21 19:31:23 +02:00
Niels van Velzen
8cbbff03da Show about section on AuthPreferences launched from login UI 2021-10-21 17:43:12 +02:00
Niels van Velzen
885f277b25 Use link in about section 2021-10-21 17:43:12 +02:00
Niels van Velzen
a292045091 Show licenses from used libraries 2021-10-21 17:43:12 +02:00
Niels van Velzen
add71ce23d Don't load multiple display preferences at once 2021-10-21 15:20:00 +02:00
Niels van Velzen
365457dd54 Add a daydream that shows random movies and series 2021-10-21 13:17:48 +02:00
Niels van Velzen
1975d26f7c Remove broken RemoteControlReceiver 2021-10-21 08:58:04 +02:00
Niels van Velzen
eedfc89998 Remove MusicOnly argument from search 2021-10-21 08:57:41 +02:00
Niels van Velzen
4779f4141a Remove search shortcut when it doesn't make sense 2021-10-21 08:57:41 +02:00
Niels van Velzen
436f317361 Use UserSettingPreferences in HomeFragment 2021-10-21 08:57:25 +02:00
Niels van Velzen
6c34edd7f8 Add PreferenceEnum interface to add different string representation for enums 2021-10-21 08:57:25 +02:00
Niels van Velzen
af114b20e0 Use context instead of activity in InfoLayoutHelper 2021-10-21 08:56:20 +02:00
Niels van Velzen
f239616ef0 Add ModelCompat to convert apiclient types to SDK types 2021-10-21 08:56:03 +02:00
Dominik
bb52fe3746 Translated using Weblate (German)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2021-10-20 20:05:29 -04:00
BacalhauSalgado
5ee3fe2b4c Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2021-10-20 20:05:29 -04:00
Csaba
c9f7c9f665 Translated using Weblate (Hungarian)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-10-20 03:05:29 -04:00
Bill Thornton
4d501fee4c Merge pull request #1211 from nielsvanvelzen/async-auto-bitrate
Don't wait for bitrate detection on launch
2021-10-19 16:35:36 -04:00
Bill Thornton
48cb855e4b Merge pull request #1212 from nielsvanvelzen/exo-default
Change default video player to ExoPlayer instead of auto detect
2021-10-19 16:33:56 -04:00
Niels van Velzen
65f4b75673 Change default video player to ExoPlayer instead of auto detect 2021-10-19 22:27:31 +02:00
Niels van Velzen
7fc63f56d8 Don't wait for bitrate detection on launch
Also catch errors
2021-10-19 22:23:02 +02:00
newton181
496b067e14 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2021-10-19 16:05:29 -04:00
newton181
badc038af5 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2021-10-19 16:05:29 -04:00
newton181
d0e24171a1 Translated using Weblate (Spanish)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-10-19 16:05:29 -04:00
Bill Thornton
ee922ac7ec Merge pull request #1197 from nielsvanvelzen/sdk-1.1
Migrate to Jellyfin Kotlin SDK v1.1
2021-10-19 11:13:38 -04:00
Matthieu
23f90f626f Translated using Weblate (French)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2021-10-19 09:05:28 -04:00
Niels van Velzen
960212b832 Migrate to Jellyfin Kotlin SDK v1.1 2021-10-18 19:08:14 +02:00
Cameron
b59543c05e "select" button on controller can now pause/unpause without extra navigation (#1207)
* fixed issue #1101

overrides PlaybackTransportRowPresenter's onProgressBarClicked to use CustomPlaybackTransportControlGlue's playPauseAction. Perhaps the lowercase 'p' is to blame for the default implementation not working.

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/CustomPlaybackTransportControlGlue.java

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2021-10-18 19:00:19 +02:00
WWWesten
a632cc1356 Translated using Weblate (Russian)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-18 09:05:28 -04:00
wolong gl
21855f5179 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2021-10-18 04:05:28 -04:00
Bill Thornton
0d94b187c5 Merge pull request #1203 from nielsvanvelzen/nowplayingbug-context 2021-10-17 11:28:33 -04:00
Bill Thornton
e21fca973d Merge pull request #1204 from nielsvanvelzen/apibinder-trailing-slash 2021-10-17 11:27:12 -04:00
Bill Thornton
05ff681cc8 Merge pull request #1188 from nielsvanvelzen/handle-setActiveSession
Act when setActiveSession returns false during login
2021-10-17 01:05:54 -04:00
Bill Thornton
65d00ef0ac Merge pull request #1011 from nielsvanvelzen/displaypreferences-sdk
Use DisplayPreferences via SDK via preferences code
2021-10-17 01:03:00 -04:00
Niels van Velzen
cb43e9024b Fix trailing slashes causing connection issues in apiclient 2021-10-16 17:33:59 +02:00
WWWesten
d236a263b7 Translated using Weblate (Russian)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-16 11:05:28 -04:00
WWWesten
c017076500 Translated using Weblate (Kazakh)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-10-16 11:05:28 -04:00
Lukáš Kucharczyk
d4ccdb8660 Translated using Weblate (Czech)
Currently translated at 100.0% (441 of 441 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2021-10-16 11:05:27 -04:00
Daniel
826e1eddc9 Add multiple versions support (#1155)
* Add multiple versions support

* Fix code format

* Fix import

* change button name

* Move function

* remove unnecessary api call

* Update formatting, rename a few variables, fix popup theme

Co-authored-by: Niels van Velzen <git@ndat.nl>
2021-10-16 10:16:56 +00:00
Niels van Velzen
3f94e31bae Use context.startActivity in NowPlayingBug 2021-10-16 11:29:16 +02:00
newton181
f062ca5637 Translated using Weblate (Spanish)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2021-10-14 17:05:27 -04:00
newton181
0312fe83e7 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2021-10-14 17:05:27 -04:00
newton181
4756cf3606 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2021-10-14 17:05:27 -04:00
ElegyD
37750df511 Fix several lint issues (#1180)
* Fix several lint issues

* Added more missing annotations
2021-10-14 13:15:47 +02:00
Hítalo Ramon
07d04f00b2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2021-10-13 22:05:26 -04:00
Hítalo Ramon
a35b90b26d Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.3% (437 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2021-10-13 19:50:19 -04:00
ButterflyOfFire
bc878e6fab Translated using Weblate (Arabic)
Currently translated at 93.4% (411 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2021-10-12 08:05:26 -04:00
Herman Andgart
037a52a4f0 Translated using Weblate (Swedish)
Currently translated at 99.7% (439 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2021-10-10 18:05:25 -04:00
Niels van Velzen
283f75f5c7 Act when setActiveSession returns false during login 2021-10-09 11:22:24 +02:00
hoanghuy309
69a64f1efb Translated using Weblate (Vietnamese)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2021-10-08 17:05:25 -04:00
Robert
878fa31b88 Translated using Weblate (German)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2021-10-07 23:05:25 -04:00
Bill Thornton
fe2dc0873c Merge pull request #1173 from nielsvanvelzen/apiclient-extensions
Remove unused LoadingState class
2021-10-06 23:40:30 -04:00
Bill Thornton
3e446b605d Merge pull request #1142 from nielsvanvelzen/resume-in-watch-next 2021-10-05 16:47:47 -04:00
Niels van Velzen
340188a1a1 Add enum names 2021-10-05 22:04:35 +02:00
Niels van Velzen
25f6c308ff Rename .java to .kt 2021-10-05 22:04:34 +02:00
Niels van Velzen
b37751a3e6 Add UserSettingPreferences for forward/backward speed. Fix livetv preferences not populated. 2021-10-05 21:52:13 +02:00
Niels van Velzen
2696f910b6 Fix rebase issues 2021-10-05 21:52:03 +02:00
Niels van Velzen
b5a653fd69 Fix cached preferences when user changes 2021-10-05 21:51:49 +02:00
Niels van Velzen
b5d52dbbf2 Migrate LiveTvPrefs to new display preference code 2021-10-05 21:51:48 +02:00
Niels van Velzen
c3749039db Move blocking functions 2021-10-05 21:51:47 +02:00
Niels van Velzen
a1360b4c82 Remove displayPrefsCache and deprecate TvApp DisplayPrefs methods 2021-10-05 21:51:46 +02:00
Niels van Velzen
a1f69be48f Remove now unused display preference preloading for libraries 2021-10-05 21:51:45 +02:00
Niels van Velzen
e5409713b2 Rename LibraryDisplayPreferences to shorter LibraryDisplayPreferences 2021-10-05 21:51:44 +02:00
Niels van Velzen
cb9e2352f8 Move filter and sort options to LibraryDisplayPreferences 2021-10-05 21:51:43 +02:00
Niels van Velzen
91f96a56fc Support async stores automatically in OptionsFragment 2021-10-05 21:51:42 +02:00
Niels van Velzen
c72e68da7e Add LibraryDisplayPreferences and use it 2021-10-05 21:51:41 +02:00
Niels van Velzen
fa458b328b Retrieve display preferences directly in HomeFragment 2021-10-05 21:51:40 +02:00
Niels van Velzen
33d8b81a8e Add AsyncPreferenceStore and DisplayPreferencesStore to asynchronously store preferences using the DisplayPreferences API 2021-10-05 21:51:39 +02:00
Niels van Velzen
f7bb0d183a Move getDefaultValue and reset function implementations to PreferenceStore interface 2021-10-05 21:51:36 +02:00
Niels van Velzen
379e3e7c2c Fix skipBackwardLength instead of skipBackLength (#1182) 2021-10-05 20:31:05 +02:00
Oatavandi
6c8d71b2b5 Translated using Weblate (Tamil)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2021-10-05 11:05:24 -04:00
Niels van Velzen
2cccc53462 Remove unused ApiClient extensions 2021-10-05 09:20:06 +02:00
Bill Thornton
c6b40ec71d Merge pull request #1172 from nielsvanvelzen/sdk-uuid-serializer 2021-10-04 17:31:24 -04:00
Bill Thornton
c76a5c9681 Merge pull request #1022 from nielsvanvelzen/no-tvapp
Remove TvApp.getApplication usages in util package
2021-10-04 17:27:39 -04:00
DanielPikilidis
c2214b4fca Translated using Weblate (Greek)
Currently translated at 71.1% (313 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2021-10-04 15:05:24 -04:00
Niels van Velzen
c3785fe433 Fix imports from bad rebase 2021-10-04 21:01:52 +02:00
Bill Thornton
a9bde16949 Merge pull request #1053 from nielsvanvelzen/baseitemdto-extras
Remove GSON usages when (de)serializing activity extras
2021-10-04 13:28:36 -04:00
ElegyD
bb7319db0c Explicitly specify android:exported (#1178) 2021-10-04 18:45:39 +02:00
Niels van Velzen
11428fa19e Java shenanigans 2021-10-04 17:00:41 +02:00
Bill Thornton
cb0fa6f31d Merge pull request #1166 from nielsvanvelzen/issue-template
Refactor issue templates to use forms
2021-10-04 10:16:17 -04:00
Csaba
26082c9a9b Translated using Weblate (Hungarian)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2021-10-04 04:05:23 -04:00
Bill Thornton
16a5eeb460 Merge pull request #1174 from nielsvanvelzen/player-fragment-nullable
Check nullability for mFragment in PlaybackController
2021-10-03 23:48:23 -04:00
wolong gl
57bba26420 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2021-10-03 14:05:23 -04:00
Niels van Velzen
f9fa23953f Check nullability for mFragment in PlaybackController 2021-10-03 16:32:18 +02:00
WWWesten
06d5a88d07 Translated using Weblate (Russian)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-03 09:05:23 -04:00
WWWesten
de2531d07c Translated using Weblate (Kazakh)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-10-03 09:05:23 -04:00
Niels van Velzen
468d4985e7 Replace UUIDSerializer with SDK provided version 2021-10-03 12:18:44 +02:00
Niels van Velzen
467f1f2b7a Translated using Weblate (Dutch)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2021-10-03 06:08:37 -04:00
Niels van Velzen
928ca45698 Translated using Weblate (Dutch)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2021-10-03 06:06:30 -04:00
Niels van Velzen
fd812d0a11 Translated using Weblate (English)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en/
2021-10-03 06:01:31 -04:00
Niels van Velzen
dd61f75827 Translated using Weblate (Dutch)
Currently translated at 100.0% (440 of 440 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2021-10-03 05:57:43 -04:00
Weblate
35ee15fe3d Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2021-10-02 16:15:41 -04:00
Bill Thornton
9c16681127 Merge pull request #1165 from nielsvanvelzen/version-build-str 2021-10-02 16:15:37 -04:00
Weblate
f341431d4e Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2021-10-02 16:00:09 -04:00
Niels van Velzen
a4b9d397a4 Remove two unused strings and two unused icons (#1169) 2021-10-02 22:00:04 +02:00
Oatavandi
b08abc7356 Translated using Weblate (Tamil)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2021-10-02 13:05:23 -04:00
WWWesten
1df5672629 Translated using Weblate (Russian)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2021-10-02 13:05:23 -04:00
WWWesten
ffc005b92f Translated using Weblate (Kazakh)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/kk/
2021-10-02 13:05:23 -04:00
Niels van Velzen
8a148398cd Add media playback bug report form template 2021-10-01 22:03:11 +02:00
Niels van Velzen
d3e1dbe775 Add feature request form template 2021-10-01 22:03:11 +02:00
Niels van Velzen
f404443ff1 Add bug report form template 2021-10-01 21:49:25 +02:00
Niels van Velzen
9e48578366 Remove build number from internal version reference
This fixes the "server version not supported" toast from showing the build number. 10.7.7.0 now shows as 10.7.7
2021-10-01 21:36:37 +02:00
Bartłomiej Ruk
6f626ef608 Translated using Weblate (Polish)
Currently translated at 99.7% (443 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2021-10-01 15:05:22 -04:00
Niels van Velzen
e3c25dc2fe Remove TvApp.getApplication usages in util package 2021-10-01 19:06:38 +02:00
Niels van Velzen
326845abff Remove GSON usages when (de)serializing for extras 2021-10-01 18:50:02 +02:00
Bill Thornton
e4815bc003 Merge pull request #1023 from nielsvanvelzen/koin-3
Migrate to Koin 3
2021-09-30 23:38:03 -04:00
Niels van Velzen
2826a4f6ba Translated using Weblate (Bengali (Bangladesh))
Currently translated at 20.4% (91 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bn_BD/
2021-09-30 13:30:08 -04:00
Niels van Velzen
04cc64249a Translated using Weblate (Lithuanian)
Currently translated at 10.5% (47 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/lt/
2021-09-30 13:30:08 -04:00
sephrat
c82644ed2e Translated using Weblate (French)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2021-09-30 13:30:06 -04:00
Lukáš Kucharczyk
52caf6734d Translated using Weblate (Czech)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2021-09-30 13:30:05 -04:00
Niels van Velzen
500cd55e23 Migrate to Koin 3 2021-09-30 14:35:53 +02:00
Niels van Velzen
fb3863dc2e Translated using Weblate (English)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en/
2021-09-30 08:21:43 -04:00
Bill Thornton
5f58651670 Merge pull request #1050 from nielsvanvelzen/agp-700 2021-09-30 08:13:57 -04:00
Niels van Velzen
214be75044 Translated using Weblate (English)
Currently translated at 93.6% (416 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en/
2021-09-30 08:08:16 -04:00
Niels van Velzen
2859b7f2b6 Update Android Gradle Plugin to 7.0.2 2021-09-30 13:42:23 +02:00
Chikku Cheriyan
445a964935 Translated using Weblate (Malayalam)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ml/
2021-09-30 00:30:05 -04:00
Tim040
3e6eb12178 Translated using Weblate (Dutch)
Currently translated at 100.0% (444 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2021-09-29 13:30:05 -04:00
Tim040
8c5e85e088 Translated using Weblate (Dutch)
Currently translated at 98.8% (439 of 444 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2021-09-28 18:30:05 -04:00
Niels van Velzen
4c5fec38cb Set "my media" row as a default row 2021-09-10 21:56:36 +02:00
Niels van Velzen
1f1628bc50 Add resume episodes to watch next row 2021-09-10 21:53:54 +02:00
344 changed files with 12778 additions and 9027 deletions

View File

@@ -2,8 +2,16 @@ root = true
[*]
insert_final_newline = true
trim_trailing_whitespace = true
[{*.kts, *.kt}]
charset = utf-8
indent_style = tab
tab_width = 4
# Disable wildcard imports in IntelliJ/Android Studio
ij_kotlin_name_count_to_use_star_import = 1000
ij_kotlin_name_count_to_use_star_import_for_members = 1000
ij_kotlin_packages_to_use_import_on_demand =
[*.md]
trim_trailing_whitespace = false

View File

@@ -0,0 +1,83 @@
name: Media playback bug report
description: Create a bug report related to media playback
labels:
- bug
- playback
body:
- type: textarea
id: description
attributes:
label: Describe the bug
description: |
A clear and concise description of the bug, including steps to reproduce it and the normally expected behavior.
You can also attach screenshots or screen recordings to help explain your issue.
placeholder: |
1. Go to …
2. Click on …
3. Scroll down to …
4. See error / the app crashes
Instead, I expect …
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: |
Please paste your client logs (logcat) here, *NOT* server logs (in most cases).
Learn how to capture those logcats [here](https://wiki.lineageos.org/logcat.html).
Make sure that they don't contain any sensitive information like server URL, auth tokens or passwords.
placeholder: Paste logs…
render: shell
- type: textarea
id: logs-ffmpeg
attributes:
label: FFmpeg logs
description: |
Please paste your FFmpeg logs here. You can find these by going to the dashboard of your Jellyfin server.
placeholder: Paste logs…
render: shell
- type: textarea
id: mediainfo
attributes:
label: Media info of the file
description: |
Please share the media information for the file causing issues. You can use a variety of tools to retrieve this information.
- Use ffprobe (`ffprobe ./file.mp4`)
- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo)
- Copy the media info from the web interface
placeholder: Paste media info…
render: shell
- type: input
id: app-version
attributes:
label: Application version
description: The version of the installed Jellyfin Android TV app.
placeholder: 0.12.0
validations:
required: true
- type: input
id: device-info
attributes:
label: Device information
description: Manufacturer and model
placeholder: Nvidia Shield Pro (2017), Amazon Fire TV Stick v1 (2014)
validations:
required: true
- type: input
id: android-version
attributes:
label: Android version
description: Version of the OS and other information (e.g. custom ROM / OEM skin)
placeholder: Android 9, Fire OS 7
validations:
required: true
- type: input
id: server-version
attributes:
label: Jellyfin server version
description: If on unstable, please specify the commit hash.
placeholder: 10.7.7
validations:
required: true

72
.github/ISSUE_TEMPLATE/bug-report.yaml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Bug report
description: Create a bug report
labels:
- bug
body:
- type: textarea
id: description
attributes:
label: Describe the bug
description: |
A clear and concise description of the bug, including steps to reproduce it and the normally expected behavior.
You can also attach screenshots or screen recordings to help explain your issue.
placeholder: |
1. Go to …
2. Click on …
3. Scroll down to …
4. See error / the app crashes
Instead, I expect …
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: |
Please paste your client logs (logcat) here, *NOT* server logs (in most cases).
Learn how to capture those logcats [here](https://wiki.lineageos.org/logcat.html).
Make sure that they don't contain any sensitive information like server URL, auth tokens or passwords.
placeholder: Paste logs…
render: shell
- type: input
id: app-version
attributes:
label: Application version
description: The version of the installed Jellyfin Android TV app.
placeholder: 0.12.0
validations:
required: true
- type: dropdown
id: installation-source
attributes:
label: Where did you install the app from?
description: Choose the appropriate app store or installation method.
options:
- Google Play
- Amazon Appstore
- Sideloaded APK
- type: input
id: device-info
attributes:
label: Device information
description: Manufacturer and model
placeholder: Nvidia Shield Pro (2017), Amazon Fire TV Stick v1 (2014)
validations:
required: true
- type: input
id: android-version
attributes:
label: Android version
description: Version of the OS and other information (e.g. custom ROM / OEM skin)
placeholder: Android 9, Fire OS 7
validations:
required: true
- type: input
id: server-version
attributes:
label: Jellyfin server version
description: If on unstable, please specify the commit hash.
placeholder: 10.7.7
validations:
required: true

View File

@@ -1,36 +0,0 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
<!-- Please paste any log errors. -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):**
- Android TV or Fire TV version: [e.g. Android 9 or Fire OS 7]
- Device manufacturer: [e.g. Amazon, Nvidia, Sony]
- Device model: [e.g. Fire TV Stick (2nd Gen) or Shield TV]
- Jellyfin server version: [e.g. 10.5.4]
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@@ -1,4 +1,4 @@
blank_issues_enabled: true
blank_issues_enabled: false
contact_links:
- name: Jellyfin documentation
url: https://jellyfin.org/docs/general/getting-help.html

View File

@@ -0,0 +1,14 @@
name: Feature request
description: Request a new feature
labels:
- enhancement
body:
- type: textarea
id: description
attributes:
label: Describe the feature you'd like
description: |
A clear and concise description of what you want to request.
You can also attach screenshots or screen recordings to help explain your request.
validations:
required: true

View File

@@ -1,14 +0,0 @@
---
name: Feature Request
about: Request a new feature
title: ''
labels: enhancement
assignees: ''
---
**Describe the feature you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

View File

@@ -1,29 +0,0 @@
---
name: Media playback issue
about: Create a media playback issue report
title: ''
labels: playback
assignees: ''
---
**Checks**
- [ ] Issue does not happen in the browser (jellyfin-web)
**Media Info of the file**
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
**Logs**
<!-- Please paste any log messages from during the playback issue. -->
**FFmpeg Logs**
<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
**System (please complete the following information):**
- Android TV or Fire TV version: [e.g. Android 9 or Fire OS 7]
- Device manufacturer: [e.g. Amazon, Nvidia, Sony]
- Device model: [e.g. Fire TV Stick (2nd Gen) or Shield TV]
- Jellyfin server version: [e.g. 10.5.4]
**Additional context**
<!-- Add any other context about the problem here. -->

19
.github/stale.yml vendored
View File

@@ -1,19 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 21
# Issues with these labels will never be considered stale
exemptLabels:
- enhancement
- confirmed
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
with:

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
with:

View File

@@ -11,7 +11,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin-androidtv' }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
with:
@@ -20,9 +20,9 @@ jobs:
- name: Set JELLYFIN_VERSION
run: echo "JELLYFIN_VERSION=$(echo ${GITHUB_REF#refs/tags/v} | tr / -)" >> $GITHUB_ENV
- name: Assemble release files
run: ./gradlew --no-daemon --info assemble versionTxt
- name: Sign proprietary APK
id: sign
run: ./gradlew --no-daemon --info assemble bundleRelease versionTxt
- name: Sign APK
id: signApk
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/release
@@ -30,12 +30,22 @@ jobs:
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Sign AAB
id: signAab
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Prepare release archive
run: |
mkdir -p build/jellyfin-publish
mv app/build/outputs/apk/*/jellyfin-androidtv-*-debug.apk build/jellyfin-publish/
mv app/build/outputs/apk/*/jellyfin-androidtv-*-release-unsigned.apk build/jellyfin-publish/
mv ${{ steps.sign.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-androidtv-v${{ env.JELLYFIN_VERSION }}-release.apk
mv ${{ steps.signApk.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-androidtv-v${{ env.JELLYFIN_VERSION }}-release.apk
mv ${{ steps.signAab.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-androidtv-v${{ env.JELLYFIN_VERSION }}-release.aab
mv app/build/version.txt build/jellyfin-publish/
- name: Upload release archive to GitHub release
uses: alexellis/upload-assets@0.3.0
@@ -44,7 +54,7 @@ jobs:
with:
asset_paths: '["build/jellyfin-publish/*"]'
- name: Upload release archive to repo.jellyfin.org
uses: burnett01/rsync-deployments@5.1
uses: burnett01/rsync-deployments@5.2
with:
switches: -vrptz
path: build/jellyfin-publish/

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
with:

View File

@@ -6,11 +6,11 @@ on:
jobs:
update:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
if: github.repository == 'jellyfin/jellyfin-androidtv'
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1
with:

View File

@@ -10,9 +10,9 @@ on:
jobs:
validate:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1

31
.github/workflows/repo-stale.yaml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Issue stale check
on:
schedule:
- cron: '30 3 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-20.04
if: github.repository == 'jellyfin/jellyfin-androidtv'
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
days-before-stale: 120
days-before-pr-stale: -1
days-before-close: 21
days-before-pr-close: -1
exempt-issue-labels: enhancement,confirmed
stale-issue-label: stale
stale-issue-message: |-
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).

View File

@@ -1,19 +1,21 @@
plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.serialization") version Plugins.Versions.kotlin
kotlin("kapt")
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.aboutlibraries)
}
android {
compileSdkVersion(30)
namespace = "org.jellyfin.androidtv"
compileSdk = 31
defaultConfig {
// Android version targets
minSdkVersion(21)
targetSdkVersion(30)
minSdk = 21
targetSdk = 31
// Release version
applicationId = namespace
versionName = project.getVersionName()
versionCode = getVersionCode(versionName!!)
setProperty("archivesBaseName", "jellyfin-androidtv-v$versionName")
@@ -34,8 +36,10 @@ android {
val release by getting {
isMinifyEnabled = false
// Add applicationId as string for XML resources
resValue("string", "app_id", "org.jellyfin.androidtv")
// Set package names used in various XML files
resValue("string", "app_id", namespace!!)
resValue("string", "app_search_suggest_authority", "${namespace}.content")
resValue("string", "app_search_suggest_intent_data", "content://${namespace}.content/intent")
// Set flavored application name
resValue("string", "app_name", "@string/app_name_release")
@@ -47,8 +51,10 @@ android {
// Use different application id to run release and debug at the same time
applicationIdSuffix = ".debug"
// Add applicationId as string for XML resources
resValue("string", "app_id", "org.jellyfin.androidtv.debug")
// Set package names used in various XML files
resValue("string", "app_id", namespace + applicationIdSuffix)
resValue("string", "app_search_suggest_authority", "${namespace + applicationIdSuffix}.content")
resValue("string", "app_search_suggest_intent_data", "content://${namespace + applicationIdSuffix}.content/intent")
// Set flavored application name
resValue("string", "app_name", "@string/app_name_debug")
@@ -57,10 +63,15 @@ android {
}
}
lintOptions {
lint {
lintConfig = file("$rootDir/android-lint.xml")
isAbortOnError = false
abortOnError = false
sarifReport = true
checkDependencies = true
}
testOptions.unitTests.all {
it.useJUnitPlatform()
}
}
@@ -107,6 +118,7 @@ dependencies {
implementation(libs.bundles.androidx.lifecycle)
implementation(libs.androidx.window)
implementation(libs.androidx.cardview)
implementation(libs.androidx.startup)
// Dependency Injection
implementation(libs.bundles.koin)
@@ -116,6 +128,7 @@ dependencies {
// Media players
implementation(libs.exoplayer)
implementation(libs.jellyfin.exoplayer.ffmpegextension)
implementation(libs.libvlc)
// Image utility
@@ -126,9 +139,12 @@ dependencies {
// Crash Reporting
implementation(libs.bundles.acra)
// Licenses
implementation(libs.aboutlibraries)
// Logging
implementation(libs.timber)
implementation(libs.slf4j.android)
implementation(libs.slf4j.timber)
// Debugging
if (getProperty("leakcanary.enable")?.toBoolean() == true)
@@ -138,6 +154,7 @@ dependencies {
coreLibraryDesugaring(libs.android.desugar)
// Testing
testImplementation(libs.junit)
testImplementation(libs.mockito)
testImplementation(libs.kotest.runner.junit5)
testImplementation(libs.kotest.assertions)
testImplementation(libs.mockk)
}

View File

@@ -59,26 +59,61 @@
android:resource="@xml/authenticator" />
</service>
<receiver android:name=".util.RemoteControlReceiver">
<service
android:name=".integration.dream.LibraryDreamService"
android:exported="true"
android:permission="android.permission.BIND_DREAM_SERVICE">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.service.dreams.DreamService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</service>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
android:initOrder="1000"
tools:node="merge">
<meta-data
android:name="org.jellyfin.androidtv.LogInitializer"
android:value="androidx.startup" />
<meta-data
android:name="org.jellyfin.androidtv.di.KoinInitializer"
android:value="androidx.startup" />
<meta-data
android:name="org.jellyfin.androidtv.SessionInitializer"
android:value="androidx.startup" />
</provider>
<provider
android:name=".integration.MediaContentProvider"
android:authorities="${applicationId}.content"
android:exported="true"
android:initOrder="10" />
<activity
android:name=".ui.browsing.MainActivity"
android:screenOrientation="landscape" />
<activity
android:name=".ui.startup.StartupActivity"
android:exported="true"
android:noHistory="true"
android:screenOrientation="landscape"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity-alias
@@ -102,11 +137,7 @@
android:screenOrientation="landscape" />
<activity
android:name=".ui.browsing.ByLetterActivity"
android:screenOrientation="landscape" />
<activity
android:name=".ui.browsing.ByGenreActivity"
android:name=".ui.browsing.GroupedItemsActivity"
android:screenOrientation="landscape" />
<activity
@@ -117,17 +148,9 @@
android:name=".ui.browsing.SuggestedMoviesActivity"
android:screenOrientation="landscape" />
<activity
android:name=".ui.browsing.BrowsePersonsActivity"
android:screenOrientation="landscape" />
<activity
android:name=".ui.search.SearchActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
</activity>
android:screenOrientation="landscape" />
<activity
android:name=".ui.itemdetail.FullDetailsActivity"

View File

@@ -1,13 +1,18 @@
package org.jellyfin.androidtv
import android.content.Context
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.*
import androidx.work.BackoffPolicy
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.await
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.acra.config.dialog
import org.acra.config.httpSender
import org.acra.config.limiter
@@ -15,78 +20,44 @@ import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.jellyfin.androidtv.auth.SessionRepository
import org.jellyfin.androidtv.di.*
import org.jellyfin.androidtv.integration.LeanbackChannelWorker
import org.jellyfin.androidtv.util.AutoBitrate
import org.koin.android.ext.android.get
import org.koin.android.ext.android.getKoin
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.core.KoinExperimentalAPI
import org.koin.core.context.startKoin
import timber.log.Timber
import timber.log.Timber.DebugTree
import java.util.concurrent.TimeUnit
@Suppress("unused")
class JellyfinApplication : TvApp(), LifecycleObserver {
@OptIn(KoinExperimentalAPI::class)
class JellyfinApplication : TvApp() {
override fun onCreate() {
super.onCreate()
// Enable improved logging for leaking resources
// https://wh0.github.io/2020/08/12/closeguard.html
if (BuildConfig.DEBUG) {
try {
Class.forName("dalvik.system.CloseGuard")
.getMethod("setEnabled", Boolean::class.javaPrimitiveType)
.invoke(null, true)
} catch (e: ReflectiveOperationException) {
@Suppress("TooGenericExceptionThrown")
throw RuntimeException(e)
// Register application lifecycle events
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
/**
* Called by the Process Lifecycle when the app is created. It is called after [onCreate].
*/
override fun onCreate(owner: LifecycleOwner) {
// Register activity lifecycle callbacks
getKoin().getAll<ActivityLifecycleCallbacks>().forEach(::registerActivityLifecycleCallbacks)
}
}
// Initialize the logging library
Timber.plant(DebugTree())
Timber.i("Application object created")
/**
* Called by the Process Lifecycle when the app is activated in the foreground (activity opened).
*/
override fun onStart(owner: LifecycleOwner) {
Timber.i("Process lifecycle started")
// Dependency Injection
startKoin {
androidContext(this@JellyfinApplication)
modules(
appModule,
authModule,
activityLifecycleCallbacksModule,
playbackModule,
preferenceModule,
utilsModule
)
}
// Register lifecycle callbacks
getKoin().getAll<ActivityLifecycleCallbacks>().forEach(::registerActivityLifecycleCallbacks)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// Restore system session
get<SessionRepository>().restoreDefaultSystemSession()
}
/**
* Called by the Process Lifecycle when the app is activated in the foreground (activity opened).
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onActivate() {
Timber.i("Process Lifecycle started")
get<SessionRepository>().restoreDefaultSession()
get<SessionRepository>().restoreDefaultSession()
}
})
}
/**
* Called from the StartupActivity when the user session is started.
*/
@DelicateCoroutinesApi
suspend fun onSessionStart() {
val workManager by inject<WorkManager>()
val autoBitrate by inject<AutoBitrate>()
@@ -104,9 +75,7 @@ class JellyfinApplication : TvApp(), LifecycleObserver {
).await()
// Detect auto bitrate
withContext(Dispatchers.IO) {
autoBitrate.detect()
}
GlobalScope.launch(Dispatchers.IO) { autoBitrate.detect() }
}
override fun attachBaseContext(base: Context?) {

View File

@@ -0,0 +1,28 @@
package org.jellyfin.androidtv
import android.content.Context
import androidx.startup.Initializer
import timber.log.Timber
class LogInitializer : Initializer<Unit> {
override fun create(context: Context) {
// Enable improved logging for leaking resources
// https://wh0.github.io/2020/08/12/closeguard.html
if (BuildConfig.DEBUG) {
try {
Class.forName("dalvik.system.CloseGuard")
.getMethod("setEnabled", Boolean::class.javaPrimitiveType)
.invoke(null, true)
} catch (e: ReflectiveOperationException) {
@Suppress("TooGenericExceptionThrown")
throw RuntimeException(e)
}
}
// Initialize the logging library
Timber.plant(Timber.DebugTree())
Timber.i("Debug tree planted")
}
override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}

View File

@@ -0,0 +1,21 @@
package org.jellyfin.androidtv
import android.content.Context
import androidx.startup.AppInitializer
import androidx.startup.Initializer
import org.jellyfin.androidtv.auth.SessionRepository
import org.jellyfin.androidtv.di.KoinInitializer
@Suppress("unused")
class SessionInitializer : Initializer<Unit> {
override fun create(context: Context) {
val koin = AppInitializer.getInstance(context)
.initializeComponent(KoinInitializer::class.java)
.koin
// Restore system session
koin.get<SessionRepository>().restoreDefaultSystemSession()
}
override fun dependencies() = listOf(KoinInitializer::class.java)
}

View File

@@ -1,32 +1,18 @@
package org.jellyfin.androidtv;
import static org.koin.java.KoinJavaComponent.inject;
import android.app.Activity;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import org.jellyfin.androidtv.ui.livetv.TvManager;
import org.jellyfin.androidtv.ui.playback.PlaybackController;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.entities.DisplayPreferences;
import java.util.HashMap;
import kotlin.Lazy;
import timber.log.Timber;
public class TvApp extends Application {
public static final String DISPLAY_PREFS_APP_NAME = "ATV";
public static final int LIVE_TV_GUIDE_OPTION_ID = 1000;
public static final int LIVE_TV_RECORDINGS_OPTION_ID = 2000;
public static final int VIDEO_QUEUE_OPTION_ID = 3000;
@@ -38,12 +24,8 @@ public class TvApp extends Application {
private BaseItemDto lastPlayedItem;
private PlaybackController playbackController;
private HashMap<String, DisplayPreferences> displayPrefsCache = new HashMap<>();
private Activity currentActivity;
private Lazy<ApiClient> apiClient = inject(ApiClient.class);
@Override
public void onCreate() {
super.onCreate();
@@ -71,7 +53,6 @@ public class TvApp extends Application {
public void setCurrentUser(UserDto currentUser) {
this.currentUser.postValue(currentUser);
TvManager.clearCache();
this.displayPrefsCache = new HashMap<>();
}
/**
@@ -96,70 +77,11 @@ public class TvApp extends Application {
this.playbackController = playbackController;
}
@NonNull
public boolean canManageRecordings() {
UserDto currentUser = getCurrentUser();
return currentUser != null && currentUser.getPolicy().getEnableLiveTvManagement();
}
@NonNull
public DisplayPreferences getCachedDisplayPrefs(String key) {
return getCachedDisplayPrefs(key, DISPLAY_PREFS_APP_NAME);
}
@NonNull
public DisplayPreferences getCachedDisplayPrefs(String key, String app) {
String cacheKey = String.format("%s.%s", app, key);
return displayPrefsCache.containsKey(cacheKey) ? displayPrefsCache.get(cacheKey) : new DisplayPreferences();
}
public void updateDisplayPrefs(DisplayPreferences preferences) {
updateDisplayPrefs("ATV", preferences);
}
public void updateDisplayPrefs(String app, DisplayPreferences preferences) {
displayPrefsCache.put(String.format("%s.%s", app, preferences.getId()), preferences);
apiClient.getValue().UpdateDisplayPreferencesAsync(preferences, getCurrentUser().getId(), app, new EmptyResponse());
Timber.d("Display prefs updated for %s isFavorite: %s", preferences.getId(), preferences.getCustomPrefs().get("FavoriteOnly"));
}
public void getDisplayPrefsAsync(String key, Response<DisplayPreferences> response) {
getDisplayPrefsAsync(key, DISPLAY_PREFS_APP_NAME, response);
}
public void getDisplayPrefsAsync(final String key, String app, final Response<DisplayPreferences> outerResponse) {
String cacheKey = String.format("%s.%s", app, key);
if (displayPrefsCache.containsKey(cacheKey)) {
Timber.d("Display prefs loaded from cache %s", cacheKey);
outerResponse.onResponse(displayPrefsCache.get(cacheKey));
} else {
apiClient.getValue().GetDisplayPreferencesAsync(key, getCurrentUser().getId(), app, new Response<DisplayPreferences>() {
@Override
public void onResponse(DisplayPreferences response) {
if (response.getSortBy() == null) response.setSortBy("SortName");
if (response.getCustomPrefs() == null)
response.setCustomPrefs(new HashMap<String, String>());
displayPrefsCache.put(cacheKey, response);
Timber.d("Display prefs loaded and saved in cache %s", cacheKey);
outerResponse.onResponse(response);
}
@Override
public void onError(Exception exception) {
//Continue with defaults
Timber.e(exception, "Unable to load display prefs ");
DisplayPreferences prefs = new DisplayPreferences();
prefs.setId(key);
prefs.setSortBy("SortName");
prefs.setCustomPrefs(new HashMap<String, String>());
outerResponse.onResponse(prefs);
}
});
}
}
@Nullable
public BaseItemDto getLastPlayedItem() {
return lastPlayedItem;

View File

@@ -7,7 +7,7 @@ import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.auth.model.AccountManagerAccount
import org.jellyfin.sdk.model.serializer.toUUID
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import java.util.*
import java.util.UUID
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

View File

@@ -6,35 +6,36 @@ import kotlinx.coroutines.launch
import org.jellyfin.androidtv.JellyfinApplication
import org.jellyfin.androidtv.util.apiclient.callApi
import org.jellyfin.androidtv.util.apiclient.callApiEmpty
import org.jellyfin.androidtv.util.sdk.legacy
import org.jellyfin.apiclient.interaction.ApiClient
import org.jellyfin.apiclient.interaction.device.IDevice
import org.jellyfin.apiclient.model.apiclient.ServerInfo
import org.jellyfin.apiclient.model.dto.UserDto
import org.jellyfin.apiclient.model.entities.MediaType
import org.jellyfin.apiclient.model.session.ClientCapabilities
import org.jellyfin.apiclient.model.session.GeneralCommandType
import org.jellyfin.sdk.model.DeviceInfo
import timber.log.Timber
class ApiBinder(
private val application: JellyfinApplication,
private val api: ApiClient,
private val device: IDevice,
private val authenticationStore: AuthenticationStore,
) {
fun updateSession(session: Session?, resultCallback: (Boolean) -> Unit) {
fun updateSession(session: Session?, deviceInfo: DeviceInfo, resultCallback: ((Boolean) -> Unit)? = null) {
GlobalScope.launch(Dispatchers.IO) {
@Suppress("TooGenericExceptionCaught")
try {
val success = updateSessionInternal(session)
resultCallback(success)
val success = try {
updateSessionInternal(session, deviceInfo)
} catch (throwable: Throwable) {
Timber.e(throwable, "Unable to update legacy API session.")
resultCallback(false)
false
}
resultCallback?.invoke(success)
}
}
private suspend fun updateSessionInternal(session: Session?): Boolean {
private suspend fun updateSessionInternal(session: Session?, deviceInfo: DeviceInfo): Boolean {
if (session == null) {
application.currentUser = null
return true
@@ -46,12 +47,12 @@ class ApiBinder(
return false
}
api.device = AuthenticationDevice(device, session.userId.toString())
api.device = deviceInfo.legacy()
api.SetAuthenticationInfo(session.accessToken, session.userId.toString())
api.EnableAutomaticNetworking(ServerInfo().apply {
id = session.serverId.toString()
name = server.name
address = server.address
address = server.address.removeSuffix("/")
userId = session.userId.toString()
accessToken = session.accessToken
})

View File

@@ -1,29 +0,0 @@
package org.jellyfin.androidtv.auth
import org.jellyfin.apiclient.interaction.device.IDevice
import org.jellyfin.sdk.model.DeviceInfo
import java.security.MessageDigest
/**
* Wrapper for [IDevice] that changes the device id to a combination of the original device id
* and the username. This is neccasary to prevent the server from trashing other sessions with
* the same device id.
*/
class AuthenticationDevice(
private val parent: IDevice,
private val username: String
) : IDevice {
// Hash the deviceId because we can't use special characters as the device id is send
// via HTTP headers
override val deviceId = MessageDigest.getInstance("SHA-1").run {
update("${parent.deviceId}+${username}".toByteArray())
digest().joinToString("") { "%02x".format(it) }
}
override val deviceName = parent.deviceName
fun toDeviceInfo() = DeviceInfo(
id = deviceId,
name = deviceName
)
}

View File

@@ -1,25 +1,37 @@
package org.jellyfin.androidtv.auth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import org.jellyfin.androidtv.auth.model.*
import org.jellyfin.androidtv.auth.model.AccountManagerAccount
import org.jellyfin.androidtv.auth.model.AuthenticatedState
import org.jellyfin.androidtv.auth.model.AuthenticatingState
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
import org.jellyfin.androidtv.auth.model.AuthenticationStoreUser
import org.jellyfin.androidtv.auth.model.LoginState
import org.jellyfin.androidtv.auth.model.PrivateUser
import org.jellyfin.androidtv.auth.model.RequireSignInState
import org.jellyfin.androidtv.auth.model.Server
import org.jellyfin.androidtv.auth.model.ServerVersionNotSupported
import org.jellyfin.androidtv.auth.model.User
import org.jellyfin.androidtv.preference.AuthenticationPreferences
import org.jellyfin.androidtv.util.ImageUtils
import org.jellyfin.apiclient.interaction.device.IDevice
import org.jellyfin.androidtv.util.sdk.forUser
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.KtorClient
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.authenticateUserByName
import org.jellyfin.sdk.api.operations.ImageApi
import org.jellyfin.sdk.api.operations.UserApi
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.userApi
import org.jellyfin.sdk.model.DeviceInfo
import org.jellyfin.sdk.model.api.ImageType
import timber.log.Timber
import java.util.*
import java.util.Date
import java.util.UUID
interface AuthenticationRepository {
fun getServers(): List<Server>
fun getServer(id: UUID): Server?
fun getUsers(server: UUID): List<PrivateUser>?
fun getUser(server: UUID, user: UUID): PrivateUser?
fun saveServer(id: UUID, name: String, address: String, version: String?, loginDisclaimer: String?)
@@ -34,25 +46,29 @@ interface AuthenticationRepository {
class AuthenticationRepositoryImpl(
private val jellyfin: Jellyfin,
private val sessionRepository: SessionRepository,
private val device: IDevice,
private val accountManagerHelper: AccountManagerHelper,
private val authenticationStore: AuthenticationStore,
private val userApiClient: KtorClient,
private val userApiClient: ApiClient,
private val authenticationPreferences: AuthenticationPreferences,
private val defaultDeviceInfo: DeviceInfo,
) : AuthenticationRepository {
private val serverComparator = compareByDescending<Server> { it.dateLastAccessed }.thenBy { it.name }
private val userComparator = compareByDescending<PrivateUser> { it.lastUsed }.thenBy { it.name }
override fun getServers() = authenticationStore.getServers().map { (id, info) ->
Server(
private fun AuthenticationStoreServer.asServer(id: UUID) = Server(
id = id,
name = info.name,
address = info.address,
version = info.version,
loginDisclaimer = info.loginDisclaimer,
dateLastAccessed = Date(info.lastUsed),
)
}.sortedWith(serverComparator)
name = name,
address = address,
version = version,
loginDisclaimer = loginDisclaimer,
dateLastAccessed = Date(lastUsed),
)
override fun getServers() = authenticationStore.getServers()
.map { (id, entry) -> entry.asServer(id) }
.sortedWith(serverComparator)
override fun getServer(id: UUID) = authenticationStore.getServer(id)?.asServer(id)
private fun mapUser(
serverId: UUID,
@@ -114,7 +130,6 @@ class AuthenticationRepositoryImpl(
return authenticated
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun authenticateUser(user: User): Flow<LoginState> = flow {
Timber.d("Authenticating serverless user %s", user)
emit(AuthenticatingState)
@@ -135,7 +150,6 @@ class AuthenticationRepositoryImpl(
else emitAll(authenticateUser(user, server))
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun authenticateUser(user: User, server: Server): Flow<LoginState> = flow {
Timber.d("Authenticating user %s", user)
emit(AuthenticatingState)
@@ -149,7 +163,7 @@ class AuthenticationRepositoryImpl(
setActiveSession(user, server) -> {
// Update stored user
try {
val userInfo by UserApi(userApiClient).getCurrentUser()
val userInfo by userApiClient.userApi.getCurrentUser()
val currentUser = authenticationStore.getUser(server.id, user.id)
val updatedUser = currentUser?.copy(
@@ -165,7 +179,7 @@ class AuthenticationRepositoryImpl(
accountManagerHelper.putAccount(AccountManagerAccount(user.id, server.id, updatedUser.name, account.accessToken))
emit(AuthenticatedState)
} catch(err: ApiClientException) {
} catch (err: ApiClientException) {
Timber.e(err, "Unable to get current user data")
emit(RequireSignInState)
}
@@ -185,17 +199,15 @@ class AuthenticationRepositoryImpl(
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun login(server: Server, username: String, password: String) = flow {
if (!server.versionSupported) {
emit(ServerVersionNotSupported(server))
return@flow
}
val api = jellyfin.createApi(server.address, deviceInfo = AuthenticationDevice(device, username).toDeviceInfo())
val userApi = UserApi(api)
val api = jellyfin.createApi(server.address, deviceInfo = defaultDeviceInfo.forUser(username))
val result = try {
val response = userApi.authenticateUserByName(username, password)
val response = api.userApi.authenticateUserByName(username, password)
response.content
} catch (err: ApiClientException) {
Timber.e(err, "Unable to sign in as $username")
@@ -219,7 +231,8 @@ class AuthenticationRepositoryImpl(
)
authenticationStore.putUser(server.id, userId, updatedUser)
accountManagerHelper.putAccount(AccountManagerAccount(userId, server.id, updatedUser.name, result.accessToken))
val accountManagerAccount = AccountManagerAccount(userId, server.id, updatedUser.name, result.accessToken)
accountManagerHelper.putAccount(accountManagerAccount)
val user = PrivateUser(
id = userId,
@@ -230,8 +243,15 @@ class AuthenticationRepositoryImpl(
imageTag = userInfo.primaryImageTag,
lastUsed = Date().time,
)
setActiveSession(user, server)
emit(AuthenticatedState)
// We just added the account so it should activate properly although in rare cases it doesn't.
// this is often caused by issues in the platforms account manager
if (setActiveSession(user, server)) emit(AuthenticatedState)
else {
// Try to remove the account and ask for sign in
accountManagerHelper.removeAccount(accountManagerAccount)
emit(RequireSignInState)
}
}
override fun logout(user: User) {
@@ -244,11 +264,8 @@ class AuthenticationRepositoryImpl(
}
override fun getUserImageUrl(server: Server, user: User): String? {
val api = jellyfin.createApi(baseUrl = server.address)
val imageApi = ImageApi(api)
return user.imageTag?.let { imageTag ->
imageApi.getUserImageUrl(
jellyfin.createApi(baseUrl = server.address).imageApi.getUserImageUrl(
userId = user.id,
tag = imageTag,
imageType = ImageType.PRIMARY,

View File

@@ -2,14 +2,21 @@ package org.jellyfin.androidtv.auth
import android.content.Context
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
import org.jellyfin.androidtv.auth.model.AuthenticationStoreUser
import org.jellyfin.androidtv.util.serializer.UUIDSerializer
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import timber.log.Timber
import java.util.*
import java.util.UUID
/**
* Storage for authentication related entities. Stores servers with users inside. Should be used in
@@ -27,7 +34,7 @@ class AuthenticationStore(
private val json = Json {
encodeDefaults = true
serializersModule = SerializersModule {
contextual(UUIDSerializer)
contextual(UUIDSerializer())
}
ignoreUnknownKeys = true
}

View File

@@ -11,7 +11,7 @@ import org.jellyfin.androidtv.auth.model.AccountManagerAccount
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
import org.jellyfin.androidtv.auth.model.AuthenticationStoreUser
import org.jellyfin.androidtv.preference.SystemPreferences
import org.jellyfin.androidtv.util.serializer.UUIDSerializer
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber
@@ -32,7 +32,7 @@ class LegacyAccountMigration(
Timber.d("Starting migration of legacy credentials from $path")
val json = Json {
serializersModule = SerializersModule {
contextual(UUIDSerializer)
contextual(UUIDSerializer())
}
ignoreUnknownKeys = true
}
@@ -55,6 +55,7 @@ class LegacyAccountMigration(
address = serverAddress ?: "",
loginDisclaimer = null,
version = serverVersion,
lastRefreshed = 0,
)
)
}

View File

@@ -3,22 +3,35 @@ package org.jellyfin.androidtv.auth
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import org.jellyfin.androidtv.auth.model.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import org.jellyfin.androidtv.auth.model.ConnectedState
import org.jellyfin.androidtv.auth.model.ConnectingState
import org.jellyfin.androidtv.auth.model.PrivateUser
import org.jellyfin.androidtv.auth.model.PublicUser
import org.jellyfin.androidtv.auth.model.Server
import org.jellyfin.androidtv.auth.model.ServerAdditionState
import org.jellyfin.androidtv.auth.model.UnableToConnectState
import org.jellyfin.androidtv.auth.model.User
import org.jellyfin.androidtv.util.sdk.toPublicUser
import org.jellyfin.androidtv.util.sdk.toServer
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.operations.BrandingApi
import org.jellyfin.sdk.api.operations.SystemApi
import org.jellyfin.sdk.api.operations.UserApi
import org.jellyfin.sdk.api.client.extensions.brandingApi
import org.jellyfin.sdk.api.client.extensions.systemApi
import org.jellyfin.sdk.api.client.extensions.userApi
import org.jellyfin.sdk.discovery.RecommendedServerInfo
import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
import org.jellyfin.sdk.model.api.ServerDiscoveryInfo
import org.jellyfin.sdk.model.api.UserDto
import org.jellyfin.sdk.model.serializer.toUUID
import timber.log.Timber
import java.util.*
import java.util.Date
import java.util.UUID
interface ServerRepository {
fun getStoredServers(): List<Server>
@@ -31,7 +44,7 @@ interface ServerRepository {
suspend fun refreshServerInfo(server: Server): Boolean
companion object {
val minimumServerVersion = Jellyfin.minimumVersion
val minimumServerVersion = Jellyfin.minimumVersion.copy(build = null)
}
}
@@ -49,11 +62,10 @@ class ServerRepositoryImpl(
}.flowOn(Dispatchers.IO)
private suspend fun getServerPublicUsers(server: Server): List<PublicUser> {
val client = jellyfin.createApi(server.address)
val userApi = UserApi(client)
val api = jellyfin.createApi(server.address)
return try {
val users by userApi.getPublicUsers()
val users by api.userApi.getPublicUsers()
users.mapNotNull(UserDto::toPublicUser)
} catch (err: ApiClientException) {
Timber.e(err, "Unable to retrieve public users")
@@ -71,10 +83,10 @@ class ServerRepositoryImpl(
if (now - serverInfo.lastRefreshed < 600000 && serverInfo.version != null) return false
return try {
val client = jellyfin.createApi(server.address)
val api = jellyfin.createApi(server.address)
// Get login disclaimer
val branding by BrandingApi(client).getBrandingOptions()
val systemInfo by SystemApi(client).getPublicSystemInfo()
val branding by api.brandingApi.getBrandingOptions()
val systemInfo by api.systemApi.getPublicSystemInfo()
authenticationStore.putServer(server.id, serverInfo.copy(
name = systemInfo.serverName ?: serverInfo.name,
@@ -105,7 +117,7 @@ class ServerRepositoryImpl(
val index = users.indexOfFirst { it.id == user.id }
if (index == -1) users.add(user)
else {
users[index] = when(val currentUser = users[index]) {
users[index] = when (val currentUser = users[index]) {
is PublicUser -> user
is PrivateUser -> currentUser.copy(
name = user.name,
@@ -133,20 +145,19 @@ class ServerRepositoryImpl(
val goodRecommendations = mutableListOf<RecommendedServerInfo>()
val badRecommendations = mutableListOf<RecommendedServerInfo>()
val greatRecommendaton = jellyfin.discovery.getRecommendedServers(addressCandidates)
.firstOrNull { recommendedServer ->
when (recommendedServer.score) {
RecommendedServerInfoScore.GREAT -> true
RecommendedServerInfoScore.GOOD -> {
goodRecommendations += recommendedServer
false
}
else -> {
badRecommendations += recommendedServer
false
}
val greatRecommendaton = jellyfin.discovery.getRecommendedServers(addressCandidates).firstOrNull { recommendedServer ->
when (recommendedServer.score) {
RecommendedServerInfoScore.GREAT -> true
RecommendedServerInfoScore.GOOD -> {
goodRecommendations += recommendedServer
false
}
else -> {
badRecommendations += recommendedServer
false
}
}
}
Timber.d(buildString {
append("Recommendations: ")
@@ -160,12 +171,12 @@ class ServerRepositoryImpl(
})
val chosenRecommendation = greatRecommendaton ?: goodRecommendations.firstOrNull()
if (chosenRecommendation != null) {
if (chosenRecommendation != null && chosenRecommendation.systemInfo.isSuccess) {
// Get system info
val systemInfo = chosenRecommendation.systemInfo!!
val systemInfo = chosenRecommendation.systemInfo.getOrThrow()
// Get branding info
val api = BrandingApi(jellyfin.createApi(chosenRecommendation.address))
val api = jellyfin.createApi(chosenRecommendation.address).brandingApi
val branding by api.getBrandingOptions()
val id = systemInfo.id!!.toUUID()
@@ -180,7 +191,10 @@ class ServerRepositoryImpl(
emit(ConnectedState(id, systemInfo))
} else {
// No great or good recommendations, only add bad recommendations
emit(UnableToConnectState(addressCandidates))
val addressCandidatesWithIssues = (badRecommendations + goodRecommendations)
.groupBy { it.address }
.mapValues { (_, entry) -> entry.flatMap { server -> server.issues } }
emit(UnableToConnectState(addressCandidatesWithIssues))
}
}
}

View File

@@ -2,12 +2,18 @@ package org.jellyfin.androidtv.auth
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.runBlocking
import org.jellyfin.androidtv.preference.AuthenticationPreferences
import org.jellyfin.androidtv.preference.constant.UserSelectBehavior.*
import org.jellyfin.sdk.api.client.KtorClient
import org.jellyfin.androidtv.preference.PreferencesRepository
import org.jellyfin.androidtv.preference.constant.UserSelectBehavior.DISABLED
import org.jellyfin.androidtv.preference.constant.UserSelectBehavior.LAST_USER
import org.jellyfin.androidtv.preference.constant.UserSelectBehavior.SPECIFIC_USER
import org.jellyfin.androidtv.util.sdk.forUser
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.DeviceInfo
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber
import java.util.*
import java.util.UUID
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -33,8 +39,10 @@ class SessionRepositoryImpl(
private val accountManagerHelper: AccountManagerHelper,
private val apiBinder: ApiBinder,
private val authenticationStore: AuthenticationStore,
private val userApiClient: KtorClient,
private val systemApiClient: KtorClient,
private val userApiClient: ApiClient,
private val systemApiClient: ApiClient,
private val preferencesRepository: PreferencesRepository,
private val defaultDeviceInfo: DeviceInfo,
) : SessionRepository {
private val _currentSession = MutableLiveData<Session?>()
override val currentSession: LiveData<Session?> get() = _currentSession
@@ -96,7 +104,7 @@ class SessionRepositoryImpl(
_currentSession.value = null
userApiClient.applySession(null)
apiBinder.updateSession(null) { }
apiBinder.updateSession(null, userApiClient.deviceInfo)
}
private fun setCurrentSession(session: Session?, includeSystemUser: Boolean, callback: ((Boolean) -> Unit)? = null) {
@@ -109,14 +117,16 @@ class SessionRepositoryImpl(
if (includeSystemUser && systemUserBehavior == LAST_USER) setCurrentSystemSession(session)
// Update session after binding the apiclient settings
apiBinder.updateSession(session) { success ->
val deviceInfo = session?.let { defaultDeviceInfo.forUser(it.userId) } ?: defaultDeviceInfo
apiBinder.updateSession(session, deviceInfo) { success ->
Timber.d("Updating current session. userId=${session?.userId} apiBindingSuccess=${success}")
if (success) {
userApiClient.applySession(session)
userApiClient.applySession(session, deviceInfo)
runBlocking { preferencesRepository.onSessionChanged() }
_currentSession.postValue(session)
} else {
userApiClient.applySession(null)
userApiClient.applySession(null, deviceInfo)
_currentSession.postValue(null)
}
@@ -151,7 +161,9 @@ class SessionRepositoryImpl(
)
}
private fun KtorClient.applySession(session: Session?) {
private fun ApiClient.applySession(session: Session?, newDeviceInfo: DeviceInfo = defaultDeviceInfo) {
deviceInfo = newDeviceInfo
if (session == null) {
baseUrl = null
accessToken = null

View File

@@ -1,6 +1,6 @@
package org.jellyfin.androidtv.auth.model
import java.util.*
import java.util.UUID
data class AccountManagerAccount(
val id: UUID,

View File

@@ -5,8 +5,9 @@ package org.jellyfin.androidtv.auth.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import org.jellyfin.androidtv.util.serializer.UUIDSerializer
import java.util.*
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import java.util.Date
import java.util.UUID
/**
* Locally stored server information. New properties require default values or deserialization will fail.

View File

@@ -2,7 +2,7 @@ package org.jellyfin.androidtv.auth.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.util.*
import java.util.Date
/**
* Locally stored user information. New properties require default values or deserialization will fail.

View File

@@ -1,23 +0,0 @@
package org.jellyfin.androidtv.auth.model
import androidx.annotation.StringRes
data class LoadingState(
val status: Status,
@StringRes val messageRes: Int? = null
) {
companion object {
val PENDING = LoadingState(Status.PENDING)
val LOADING = LoadingState(Status.LOADING)
val SUCCESS = LoadingState(Status.SUCCESS)
val ERROR = LoadingState(Status.ERROR)
fun error(@StringRes messageRes: Int? = null) = LoadingState(Status.ERROR, messageRes)
}
enum class Status {
PENDING,
LOADING,
SUCCESS,
ERROR
}
}

View File

@@ -2,7 +2,8 @@ package org.jellyfin.androidtv.auth.model
import org.jellyfin.androidtv.auth.ServerRepository
import org.jellyfin.sdk.model.ServerVersion
import java.util.*
import java.util.Date
import java.util.UUID
/**
* Server model to use locally in place of ServerInfo model in ApiClient.
@@ -18,6 +19,8 @@ data class Server(
private val serverVersion = version?.let(ServerVersion::fromString)
val versionSupported = serverVersion != null && serverVersion >= ServerRepository.minimumServerVersion
operator fun compareTo(other: ServerVersion): Int = serverVersion?.compareTo(other) ?: -1
override fun equals(other: Any?) = other is Server
&& id == other.id
&& address == other.address

View File

@@ -1,9 +1,10 @@
package org.jellyfin.androidtv.auth.model
import org.jellyfin.sdk.discovery.RecommendedServerIssue
import org.jellyfin.sdk.model.api.PublicSystemInfo
import java.util.*
import java.util.UUID
sealed class ServerAdditionState
data class ConnectingState(val address: String) : ServerAdditionState()
data class UnableToConnectState(val addressCandidates: List<String>) : ServerAdditionState()
data class UnableToConnectState(val addressCandidates: Map<String, Collection<RecommendedServerIssue>>) : ServerAdditionState()
data class ConnectedState(val id: UUID, val publicInfo: PublicSystemInfo) : ServerAdditionState()

View File

@@ -1,6 +1,6 @@
package org.jellyfin.androidtv.auth.model
import java.util.*
import java.util.UUID
/**
* User model used locally.

View File

@@ -5,18 +5,22 @@ public class CodecTypes {
public static final String AAC = "aac";
public static final String AAC_LATM = "aac_latm";
public static final String AC3 = "ac3";
public static final String ALAC = "alac";
public static final String APE = "ape";
public static final String DCA = "dca";
public static final String DTS = "dts";
public static final String EAC3 = "eac3";
public static final String FLAC = "flac";
public static final String MLP = "mlp";
public static final String MP2 = "mp2";
public static final String MP3 = "mp3";
public static final String MPA = "mpa";
public static final String OPUS = "opus";
public static final String PCM = "pcm";
public static final String PCM_S16LE = "PCM_S16LE";
public static final String PCM_S24LE = "PCM_S24LE";
public static final String PCM_ALAW = "pcm_alaw";
public static final String PCM_MULAW = "pcm_mulaw";
public static final String PCM_S16LE = "pcm_s16le";
public static final String PCM_S24LE = "pcm_s24le";
public static final String TRUEHD = "truehd";
public static final String VORBIS = "vorbis";
public static final String WAV = "wav";

View File

@@ -2,7 +2,5 @@ package org.jellyfin.androidtv.constant;
public enum CustomMessage {
RefreshCurrentItem,
RemoveCurrentItem,
ActionComplete,
RefreshRows
}

View File

@@ -1,6 +1,6 @@
package org.jellyfin.androidtv.constant
object Extras {
const val Folder = "Folder"
const val IncludeType = "IncludeType"
const val Folder = "folder"
const val IncludeType = "type_include"
}

View File

@@ -14,9 +14,5 @@ enum class GridDirection {
* Vertical.
*/
@EnumDisplayOptions(R.string.grid_direction_vertical)
VERTICAL;
companion object {
fun getGridDirection(direction: String?) = values().firstOrNull { it.name == direction }
}
VERTICAL,
}

View File

@@ -1,22 +1,44 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.preference.PreferenceEnum
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
/**
* All possible homesections, synced with jellyfin-web
* All possible homesections, "synced" with jellyfin-web.
*
* https://github.com/jellyfin/jellyfin-web/blob/master/src/components/homesections/homesections.js
*/
enum class HomeSectionType(
val id: String
) {
override val serializedName: String
) : PreferenceEnum {
@EnumDisplayOptions(R.string.home_section_latest_media)
LATEST_MEDIA("latestmedia"),
LIBRARY_TILES_SMALL("smalllibrarytiles"),
LIBRARY_BUTTONS("librarybuttons"),
RESUME("resume"),
RESUME_AUDIO("resumeaudio"),
ACTIVE_RECORDINGS("activerecordings"),
NEXT_UP("nextup"),
LIVE_TV("livetv"),
NONE("none");
companion object {
fun getById(id: String) = values().firstOrNull { it.id == id }
}
@EnumDisplayOptions(R.string.home_section_library)
LIBRARY_TILES_SMALL("smalllibrarytiles"),
@EnumDisplayOptions(R.string.home_section_library_small)
LIBRARY_BUTTONS("librarybuttons"),
@EnumDisplayOptions(R.string.home_section_resume)
RESUME("resume"),
@EnumDisplayOptions(R.string.home_section_resume_audio)
RESUME_AUDIO("resumeaudio"),
@EnumDisplayOptions(R.string.home_section_resume_book)
RESUME_BOOK("resumebook"),
@EnumDisplayOptions(R.string.home_section_active_recordings)
ACTIVE_RECORDINGS("activerecordings"),
@EnumDisplayOptions(R.string.home_section_next_up)
NEXT_UP("nextup"),
@EnumDisplayOptions(R.string.home_section_livetv)
LIVE_TV("livetv"),
@EnumDisplayOptions(R.string.home_section_none)
NONE("none"),
}

View File

@@ -1,7 +0,0 @@
package org.jellyfin.androidtv.constant;
public class ImageType {
public static final String DEFAULT = "0";
public static final String THUMB = "1";
public static final String BANNER = "2";
}

View File

@@ -0,0 +1,24 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
enum class ImageType {
/**
* Default.
*/
@EnumDisplayOptions(R.string.image_type_default)
DEFAULT,
/**
* Thumbnail.
*/
@EnumDisplayOptions(R.string.image_type_thumbnail)
THUMB,
/**
* Banner.
*/
@EnumDisplayOptions(R.string.image_type_banner)
BANNER
}

View File

@@ -1,8 +0,0 @@
package org.jellyfin.androidtv.constant;
public class PosterSize {
public static final String AUTO = "0";
public static final String SMALL = "1";
public static final String MED = "2";
public static final String LARGE = "3";
}

View File

@@ -0,0 +1,30 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
enum class PosterSize {
/**
* Automatic.
*/
@EnumDisplayOptions(R.string.image_size_auto)
AUTO,
/**
* Small.
*/
@EnumDisplayOptions(R.string.image_size_small)
SMALL,
/**
* Medium.
*/
@EnumDisplayOptions(R.string.image_size_medium)
MED,
/**
* Large.
*/
@EnumDisplayOptions(R.string.image_size_large)
LARGE
}

View File

@@ -10,8 +10,6 @@ public enum QueryType {
SimilarMovies,
StaticPeople,
StaticChapters,
StaticServers,
Users,
Search,
Specials,
Trailers,
@@ -24,7 +22,6 @@ public enum QueryType {
StaticAudioQueueItems,
AlbumArtists,
AudioPlaylists,
ContinueWatching,
LatestItems,
SeriesTimer,
Premieres

View File

@@ -1,6 +0,0 @@
package org.jellyfin.androidtv.constant;
public class ViewType {
public static final String SMART = "0";
public static final String GRID = "1";
}

View File

@@ -1,11 +1,11 @@
package org.jellyfin.androidtv.data.compat;
import java.util.ArrayList;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
import org.jellyfin.apiclient.model.dlna.EncodingContext;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import java.util.ArrayList;
/**
* Class AudioOptions.
*
@@ -40,26 +40,6 @@ public class AudioOptions {
EnableDirectStream = value;
}
private boolean ForceDirectPlay;
public final boolean getForceDirectPlay() {
return ForceDirectPlay;
}
public final void setForceDirectPlay(boolean value) {
ForceDirectPlay = value;
}
private boolean ForceDirectStream;
public final boolean getForceDirectStream() {
return ForceDirectStream;
}
public final void setForceDirectStream(boolean value) {
ForceDirectStream = value;
}
private String ItemId;
public final String getItemId() {
@@ -103,16 +83,6 @@ public class AudioOptions {
MediaSourceId = value;
}
private String DeviceId;
public final String getDeviceId() {
return DeviceId;
}
public final void setDeviceId(String value) {
DeviceId = value;
}
/**
* Allows an override of supported number of audio channels
* Example: DeviceProfile supports five channel, but user only has stereo speakers
@@ -154,43 +124,4 @@ public class AudioOptions {
public final void setContext(EncodingContext value) {
Context = value;
}
/**
* Gets or sets the audio transcoding bitrate.
*
* <value>The audio transcoding bitrate.</value>
*/
private Integer AudioTranscodingBitrate = null;
public final Integer getAudioTranscodingBitrate() {
return AudioTranscodingBitrate;
}
public final void setAudioTranscodingBitrate(Integer value) {
AudioTranscodingBitrate = value;
}
/**
* Gets the maximum bitrate.
*
* @return System.Nullable&lt;System.Int32&gt;.
*/
public final Integer GetMaxBitrate(boolean isAudio) {
if (getMaxBitrate() != null) {
return getMaxBitrate();
}
if (getProfile() != null) {
if (getContext() == EncodingContext.Static) {
if (isAudio && getProfile().getMaxStaticMusicBitrate() != null) {
return getProfile().getMaxStaticMusicBitrate();
}
return getProfile().getMaxStaticBitrate();
}
return getProfile().getMaxStreamingBitrate();
}
return null;
}
}

View File

@@ -1,13 +0,0 @@
package org.jellyfin.androidtv.data.compat;
import java.util.Comparator;
@Deprecated
public abstract class BaseStreamInfoSorter implements Comparator<StreamInfo> {
protected abstract int getValue(StreamInfo info);
@Override
public int compare(StreamInfo lhs, StreamInfo rhs) {
return Integer.compare(getValue(lhs), getValue(rhs));
}
}

View File

@@ -1,30 +0,0 @@
package org.jellyfin.androidtv.data.compat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@Deprecated
public class ChainedComparator<T> implements Comparator<T> {
private List<Comparator<T>> simpleComparators;
public ChainedComparator(Comparator<T>... simpleComparators) {
List<Comparator<T>> list = new ArrayList<>();
for (Comparator<T> comparator : simpleComparators) {
list.add(comparator);
}
this.simpleComparators = list;
}
public int compare(T o1, T o2) {
for (Comparator<T> comparator : simpleComparators) {
int result = comparator.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
}

View File

@@ -1,82 +0,0 @@
package org.jellyfin.androidtv.data.compat;
/**
* Class DrawingUtils
*
* @deprecated
*/
@Deprecated
public final class DrawingUtils {
/**
* Resizes a set of dimensions
*
* @param size The original size object
* @param width A new fixed width, if desired
* @param height A new fixed height, if desired
* @param maxWidth A max fixed width, if desired
* @param maxHeight A max fixed height, if desired
* @return A new size object
*/
public static ImageSize Resize(ImageSize size, Double width, Double height, Double maxWidth, Double maxHeight) {
double newWidth = size.getWidth();
double newHeight = size.getHeight();
if (width != null && height != null) {
newWidth = width;
newHeight = height;
} else if (height != null) {
newWidth = GetNewWidth(newHeight, newWidth, height);
newHeight = height;
} else if (width != null) {
newHeight = GetNewHeight(newHeight, newWidth, width);
newWidth = width;
}
if (maxHeight != null && maxHeight < newHeight) {
newWidth = GetNewWidth(newHeight, newWidth, maxHeight);
newHeight = maxHeight;
}
if (maxWidth != null && maxWidth < newWidth) {
newHeight = GetNewHeight(newHeight, newWidth, maxWidth);
newWidth = maxWidth;
}
ImageSize tempVar = new ImageSize();
tempVar.setWidth(newWidth);
tempVar.setHeight(newHeight);
return tempVar;
}
/**
* Gets the new width.
*
* @param currentHeight Height of the current.
* @param currentWidth Width of the current.
* @param newHeight The new height.
* @return System.Double.
*/
private static double GetNewWidth(double currentHeight, double currentWidth, double newHeight) {
double scaleFactor = newHeight;
scaleFactor /= currentHeight;
scaleFactor *= currentWidth;
return scaleFactor;
}
/**
* Gets the new height.
*
* @param currentHeight Height of the current.
* @param currentWidth Width of the current.
* @param newWidth The new width.
* @return System.Double.
*/
private static double GetNewHeight(double currentHeight, double currentWidth, double newWidth) {
double scaleFactor = newWidth;
scaleFactor /= currentWidth;
scaleFactor *= currentHeight;
return scaleFactor;
}
}

View File

@@ -1,93 +0,0 @@
package org.jellyfin.androidtv.data.compat;
import org.jellyfin.androidtv.util.Utils;
import timber.log.Timber;
/**
* Struct ImageSize
*
* @deprecated
*/
//C# TO JAVA CONVERTER WARNING: Java does not allow user-defined value types. The behavior of this class will differ from the original:
//ORIGINAL LINE: public struct ImageSize
@Deprecated
public final class ImageSize {
private double _height;
private double _width;
/**
* Gets or sets the height.
*
* <value>The height.</value>
*/
public double getHeight() {
return _height;
}
public void setHeight(double value) {
_height = value;
}
/**
* Gets or sets the width.
*
* <value>The width.</value>
*/
public double getWidth() {
return _width;
}
public void setWidth(double value) {
_width = value;
}
public boolean equals(ImageSize size) {
return (Double.valueOf(getWidth())).equals(size.getWidth()) && (Double.valueOf(getHeight())).equals(size.getHeight());
}
@Override
public String toString() {
return String.format("%1$s-%2$s", getWidth(), getHeight());
}
public ImageSize() {
}
public ImageSize(String value) {
_width = 0;
_height = 0;
ParseValue(value);
}
public ImageSize(int width, int height) {
_width = width;
_height = height;
}
private void ParseValue(String value) {
if (Utils.isNonEmpty(value)) {
String[] parts = value.split("[-]", -1);
if (parts.length == 2) {
try {
_width = Double.parseDouble(parts[0]);
_height = Double.parseDouble(parts[1]);
} catch (NumberFormatException ex) {
Timber.e(ex, "Could not parse width and height");
}
}
}
}
public ImageSize clone() {
ImageSize varCopy = new ImageSize();
varCopy._height = this._height;
varCopy._width = this._width;
return varCopy;
}
}

View File

@@ -1,11 +1,6 @@
package org.jellyfin.androidtv.data.compat;
import org.jellyfin.androidtv.constant.ContainerTypes;
import org.jellyfin.androidtv.constant.MediaTypes;
import org.jellyfin.androidtv.util.Utils;
import java.util.ArrayList;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
import org.jellyfin.apiclient.model.dlna.DlnaProfileType;
import org.jellyfin.apiclient.model.dlna.EncodingContext;
@@ -17,9 +12,10 @@ import org.jellyfin.apiclient.model.dto.NameValuePair;
import org.jellyfin.apiclient.model.entities.MediaStream;
import org.jellyfin.apiclient.model.entities.MediaStreamType;
import org.jellyfin.apiclient.model.mediainfo.MediaProtocol;
import org.jellyfin.apiclient.model.mediainfo.TransportStreamTimestamp;
import org.jellyfin.apiclient.model.session.PlayMethod;
import java.util.ArrayList;
/**
* Class StreamInfo.
*
@@ -95,16 +91,6 @@ public class StreamInfo {
Container = value;
}
private String SubProtocol;
public final String getSubProtocol() {
return SubProtocol;
}
public final void setSubProtocol(String value) {
SubProtocol = value;
}
private long StartPositionTicks;
public final long getStartPositionTicks() {
@@ -115,56 +101,6 @@ public class StreamInfo {
StartPositionTicks = value;
}
private String VideoCodec;
public final String getVideoCodec() {
return VideoCodec;
}
public final void setVideoCodec(String value) {
VideoCodec = value;
}
private String VideoProfile;
public final String getVideoProfile() {
return VideoProfile;
}
public final void setVideoProfile(String value) {
VideoProfile = value;
}
private boolean CopyTimestamps;
public final boolean getCopyTimestamps() {
return CopyTimestamps;
}
public final void setCopyTimestamps(boolean value) {
CopyTimestamps = value;
}
private boolean ForceLiveStream;
public final boolean getForceLiveStream() {
return ForceLiveStream;
}
public final void setForceLiveStream(boolean value) {
ForceLiveStream = value;
}
private boolean EnableSubtitlesInManifest;
public final boolean getEnableSubtitlesInManifest() {
return EnableSubtitlesInManifest;
}
public final void setEnableSubtitlesInManifest(boolean value) {
EnableSubtitlesInManifest = value;
}
private String[] AudioCodecs;
public final String[] getAudioCodecs() {
@@ -175,126 +111,6 @@ public class StreamInfo {
AudioCodecs = value;
}
private Integer AudioStreamIndex = null;
public final Integer getAudioStreamIndex() {
return AudioStreamIndex;
}
public final void setAudioStreamIndex(Integer value) {
AudioStreamIndex = value;
}
private Integer SubtitleStreamIndex = null;
public final Integer getSubtitleStreamIndex() {
return SubtitleStreamIndex;
}
public final void setSubtitleStreamIndex(Integer value) {
SubtitleStreamIndex = value;
}
private Integer TranscodingMaxAudioChannels = null;
public final Integer getTranscodingMaxAudioChannels() {
return TranscodingMaxAudioChannels;
}
public final void setTranscodingMaxAudioChannels(Integer value) {
TranscodingMaxAudioChannels = value;
}
private Integer MaxAudioChannels = null;
public final Integer getMaxAudioChannels() {
return MaxAudioChannels;
}
public final void setMaxAudioChannels(Integer value) {
MaxAudioChannels = value;
}
private Integer AudioBitrate = null;
public final Integer getAudioBitrate() {
return AudioBitrate;
}
public final void setAudioBitrate(Integer value) {
AudioBitrate = value;
}
private Integer VideoBitrate = null;
public final Integer getVideoBitrate() {
return VideoBitrate;
}
public final void setVideoBitrate(Integer value) {
VideoBitrate = value;
}
private Integer VideoLevel = null;
public final Integer getVideoLevel() {
return VideoLevel;
}
public final void setVideoLevel(Integer value) {
VideoLevel = value;
}
private Integer MaxWidth = null;
public final Integer getMaxWidth() {
return MaxWidth;
}
public final void setMaxWidth(Integer value) {
MaxWidth = value;
}
private Integer MaxHeight = null;
public final Integer getMaxHeight() {
return MaxHeight;
}
public final void setMaxHeight(Integer value) {
MaxHeight = value;
}
private Integer MaxVideoBitDepth = null;
public final Integer getMaxVideoBitDepth() {
return MaxVideoBitDepth;
}
public final void setMaxVideoBitDepth(Integer value) {
MaxVideoBitDepth = value;
}
private Integer MaxRefFrames = null;
public final Integer getMaxRefFrames() {
return MaxRefFrames;
}
public final void setMaxRefFrames(Integer value) {
MaxRefFrames = value;
}
private Float MaxFramerate = null;
public final Float getMaxFramerate() {
return MaxFramerate;
}
public final void setMaxFramerate(Float value) {
MaxFramerate = value;
}
private DeviceProfile DeviceProfile;
public final DeviceProfile getDeviceProfile() {
@@ -305,16 +121,6 @@ public class StreamInfo {
DeviceProfile = value;
}
private String DeviceProfileId;
public final String getDeviceProfileId() {
return DeviceProfileId;
}
public final void setDeviceProfileId(String value) {
DeviceProfileId = value;
}
private String DeviceId;
public final String getDeviceId() {
@@ -341,20 +147,6 @@ public class StreamInfo {
return TranscodeSeekInfo;
}
public final void setTranscodeSeekInfo(TranscodeSeekInfo value) {
TranscodeSeekInfo = value;
}
private boolean EstimateContentLength;
public final boolean getEstimateContentLength() {
return EstimateContentLength;
}
public final void setEstimateContentLength(boolean value) {
EstimateContentLength = value;
}
private MediaSourceInfo MediaSource;
public final MediaSourceInfo getMediaSource() {
@@ -371,20 +163,6 @@ public class StreamInfo {
return SubtitleDeliveryMethod;
}
public final void setSubtitleDeliveryMethod(SubtitleDeliveryMethod value) {
SubtitleDeliveryMethod = value;
}
private String SubtitleFormat;
public final String getSubtitleFormat() {
return SubtitleFormat;
}
public final void setSubtitleFormat(String value) {
SubtitleFormat = value;
}
private String PlaySessionId;
public final String getPlaySessionId() {
@@ -395,16 +173,6 @@ public class StreamInfo {
PlaySessionId = value;
}
private ArrayList<MediaSourceInfo> AllMediaSources;
public final ArrayList<MediaSourceInfo> getAllMediaSources() {
return AllMediaSources;
}
public final void setAllMediaSources(ArrayList<MediaSourceInfo> value) {
AllMediaSources = value;
}
public final String getMediaSourceId() {
return getMediaSource() == null ? null : getMediaSource().getId();
}
@@ -451,15 +219,6 @@ public class StreamInfo {
return GetUrl(baseUrl, queryString);
}
public final String ToDlnaUrl(String baseUrl, String accessToken) {
if (getPlayMethod() == PlayMethod.DirectPlay) {
return getMediaSource().getPath();
}
String dlnaCommand = BuildDlnaParam(this, accessToken);
return GetUrl(baseUrl, dlnaCommand);
}
private String GetUrl(String baseUrl, String queryString) {
if (Utils.isEmpty(baseUrl)) {
throw new IllegalArgumentException(baseUrl);
@@ -471,66 +230,41 @@ public class StreamInfo {
baseUrl = baseUrl.replaceAll("[/]+$", "");
if (getMediaType() == DlnaProfileType.Audio) {
if (MediaTypes.HLS.equalsIgnoreCase(getSubProtocol())) {
return String.format("%1$s/audio/%2$s/master.m3u8?%3$s", baseUrl, getItemId(), queryString);
}
return String.format("%1$s/audio/%2$s/stream%3$s?%4$s", baseUrl, getItemId(), extension, queryString);
}
if (MediaTypes.HLS.equalsIgnoreCase(getSubProtocol())) {
return String.format("%1$s/videos/%2$s/master.m3u8?%3$s", baseUrl, getItemId(), queryString);
}
return String.format("%1$s/videos/%2$s/stream%3$s?%4$s", baseUrl, getItemId(), extension, queryString);
}
private static String BuildDlnaParam(StreamInfo item, String accessToken) {
ArrayList<String> list = new ArrayList<String>();
for (NameValuePair pair : BuildParams(item, accessToken, true)) {
list.add(pair.getValue());
}
return String.format("Params=%1$s", Utils.join(";", list));
}
private static ArrayList<NameValuePair> BuildParams(StreamInfo item, String accessToken, boolean isDlna) {
ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
String audioCodecs = item.getAudioCodecs().length == 0 ? "" : Utils.join(",", item.getAudioCodecs());
String tempVar = item.getDeviceProfileId();
list.add(new NameValuePair("DeviceProfileId", (tempVar != null) ? tempVar : ""));
list.add(new NameValuePair("DeviceProfileId", ""));
String tempVar2 = item.getDeviceId();
list.add(new NameValuePair("DeviceId", (tempVar2 != null) ? tempVar2 : ""));
String tempVar3 = item.getMediaSourceId();
list.add(new NameValuePair("MediaSourceId", (tempVar3 != null) ? tempVar3 : ""));
list.add(new NameValuePair(STATIC, Boolean.valueOf(item.getIsDirectStream()).toString().toLowerCase()));
String tempVar4 = item.getVideoCodec();
list.add(new NameValuePair("VideoCodec", (tempVar4 != null) ? tempVar4 : ""));
list.add(new NameValuePair("VideoCodec", ""));
list.add(new NameValuePair("AudioCodec", audioCodecs));
list.add(new NameValuePair("AudioStreamIndex", item.getAudioStreamIndex() != null ? String.valueOf(item.getAudioStreamIndex()) : ""));
list.add(new NameValuePair("SubtitleStreamIndex", item.getSubtitleStreamIndex() != null && item.getSubtitleDeliveryMethod() != org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod.External ? String.valueOf(item.getSubtitleStreamIndex()) : ""));
list.add(new NameValuePair("VideoBitrate", item.getVideoBitrate() != null ? String.valueOf(item.getVideoBitrate()) : ""));
list.add(new NameValuePair("AudioBitrate", item.getAudioBitrate() != null ? String.valueOf(item.getAudioBitrate()) : ""));
list.add(new NameValuePair("MaxAudioChannels", item.getMaxAudioChannels() != null ? String.valueOf(item.getMaxAudioChannels()) : ""));
list.add(new NameValuePair("MaxFramerate", item.getMaxFramerate() != null ? String.valueOf(item.getMaxFramerate()) : ""));
list.add(new NameValuePair("MaxWidth", item.getMaxWidth() != null ? String.valueOf(item.getMaxWidth()) : ""));
list.add(new NameValuePair("MaxHeight", item.getMaxHeight() != null ? String.valueOf(item.getMaxHeight()) : ""));
list.add(new NameValuePair("AudioStreamIndex", ""));
list.add(new NameValuePair("SubtitleStreamIndex", ""));
list.add(new NameValuePair("VideoBitrate", ""));
list.add(new NameValuePair("AudioBitrate", ""));
list.add(new NameValuePair("MaxAudioChannels", ""));
list.add(new NameValuePair("MaxFramerate", ""));
list.add(new NameValuePair("MaxWidth", ""));
list.add(new NameValuePair("MaxHeight", ""));
if (MediaTypes.HLS.equalsIgnoreCase(item.getSubProtocol()) && !item.getForceLiveStream()) {
list.add(new NameValuePair(START_TIME_TICKS, ""));
} else {
list.add(new NameValuePair(START_TIME_TICKS, String.valueOf(item.getStartPositionTicks())));
}
list.add(new NameValuePair(START_TIME_TICKS, String.valueOf(item.getStartPositionTicks())));
list.add(new NameValuePair("Level", item.getVideoLevel() != null ? String.valueOf(item.getVideoLevel()) : ""));
list.add(new NameValuePair("Level", ""));
list.add(new NameValuePair("MaxRefFrames", item.getMaxRefFrames() != null ? String.valueOf(item.getMaxRefFrames()) : ""));
list.add(new NameValuePair("MaxVideoBitDepth", item.getMaxVideoBitDepth() != null ? String.valueOf(item.getMaxVideoBitDepth()) : ""));
String tempVar5 = item.getVideoProfile();
list.add(new NameValuePair("Profile", (tempVar5 != null) ? tempVar5 : ""));
list.add(new NameValuePair("MaxRefFrames", ""));
list.add(new NameValuePair("MaxVideoBitDepth", ""));
list.add(new NameValuePair("Profile", ""));
// no longer used
list.add(new NameValuePair("Cabac", ""));
@@ -546,12 +280,12 @@ public class StreamInfo {
list.add(new NameValuePair("ItemId", item.getItemId()));
}
list.add(new NameValuePair("CopyTimestamps", Boolean.valueOf(item.getCopyTimestamps()).toString().toLowerCase()));
list.add(new NameValuePair("ForceLiveStream", Boolean.valueOf(item.getForceLiveStream()).toString().toLowerCase()));
list.add(new NameValuePair("SubtitleMethod", item.getSubtitleStreamIndex() != null && item.getSubtitleDeliveryMethod() != org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod.External ? item.getSubtitleDeliveryMethod().toString() : ""));
list.add(new NameValuePair("CopyTimestamps", "false"));
list.add(new NameValuePair("ForceLiveStream", "false"));
list.add(new NameValuePair("SubtitleMethod", ""));
list.add(new NameValuePair("TranscodingMaxAudioChannels", item.getTranscodingMaxAudioChannels() != null ? String.valueOf(item.getTranscodingMaxAudioChannels()) : ""));
list.add(new NameValuePair("EnableSubtitlesInManifest", Boolean.valueOf(item.getEnableSubtitlesInManifest()).toString().toLowerCase()));
list.add(new NameValuePair("TranscodingMaxAudioChannels", ""));
list.add(new NameValuePair("EnableSubtitlesInManifest", "false"));
String tempVar7 = item.getMediaSource().getETag();
list.add(new NameValuePair("Tag", (tempVar7 != null) ? tempVar7 : ""));
@@ -559,24 +293,6 @@ public class StreamInfo {
return list;
}
public final ArrayList<SubtitleStreamInfo> GetExternalSubtitles(boolean includeSelectedTrackOnly, String baseUrl, String accessToken) {
return GetExternalSubtitles(includeSelectedTrackOnly, false, baseUrl, accessToken);
}
public final ArrayList<SubtitleStreamInfo> GetExternalSubtitles(boolean includeSelectedTrackOnly, boolean enableAllProfiles, String baseUrl, String accessToken) {
ArrayList<SubtitleStreamInfo> list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken);
ArrayList<SubtitleStreamInfo> newList = new ArrayList<SubtitleStreamInfo>();
// First add the selected track
for (SubtitleStreamInfo stream : list) {
if (stream.getDeliveryMethod() == SubtitleDeliveryMethod.External) {
newList.add(stream);
}
}
return newList;
}
public final ArrayList<SubtitleStreamInfo> GetSubtitleProfiles(boolean includeSelectedTrackOnly, String baseUrl, String accessToken) {
return GetSubtitleProfiles(includeSelectedTrackOnly, false, baseUrl, accessToken);
}
@@ -585,20 +301,11 @@ public class StreamInfo {
ArrayList<SubtitleStreamInfo> list = new ArrayList<SubtitleStreamInfo>();
// HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = MediaTypes.HLS.equalsIgnoreCase(getSubProtocol()) ? 0 : (getPlayMethod() == PlayMethod.Transcode && !getCopyTimestamps() ? getStartPositionTicks() : 0);
// First add the selected track
if (getSubtitleStreamIndex() != null) {
for (MediaStream stream : getMediaSource().getMediaStreams()) {
if (stream.getType() == MediaStreamType.Subtitle && stream.getIndex() == getSubtitleStreamIndex()) {
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
}
long startPositionTicks = getPlayMethod() == PlayMethod.Transcode ? getStartPositionTicks() : 0;
if (!includeSelectedTrackOnly) {
for (MediaStream stream : getMediaSource().getMediaStreams()) {
if (stream.getType() == MediaStreamType.Subtitle && (getSubtitleStreamIndex() == null || stream.getIndex() != getSubtitleStreamIndex())) {
if (stream.getType() == MediaStreamType.Subtitle) {
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
@@ -624,8 +331,6 @@ public class StreamInfo {
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, String baseUrl, String accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles) {
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, getPlayMethod());
SubtitleStreamInfo tempVar = new SubtitleStreamInfo();
tempVar.setIsForced(stream.getIsForced());
tempVar.setLanguage(stream.getLanguage());
String tempVar2 = stream.getLanguage();
tempVar.setName((tempVar2 != null) ? tempVar2 : "Unknown");
tempVar.setFormat(subtitleProfile.getFormat());
@@ -641,276 +346,18 @@ public class StreamInfo {
if (!Utils.isEmpty(accessToken)) {
info.setUrl(info.getUrl() + "?api_key=" + accessToken);
}
info.setIsExternalUrl(false);
} else {
info.setUrl(stream.getPath());
info.setIsExternalUrl(true);
}
}
return info;
}
/**
* Returns the audio stream that will be used
*/
public final MediaStream getTargetAudioStream() {
if (getMediaSource() != null) {
return getMediaSource().GetDefaultAudioStream(getAudioStreamIndex());
}
return null;
}
/**
* Returns the video stream that will be used
*/
public final MediaStream getTargetVideoStream() {
if (getMediaSource() != null) {
return getMediaSource().getVideoStream();
}
return null;
}
/**
* Predicts the audio sample rate that will be in the output stream
*/
public final Integer getTargetAudioSampleRate() {
MediaStream stream = getTargetAudioStream();
return stream == null ? null : stream.getSampleRate();
}
/**
* Predicts the audio sample rate that will be in the output stream
*/
public final Integer getTargetVideoBitDepth() {
MediaStream stream = getTargetVideoStream();
return stream == null || !getIsDirectStream() ? null : stream.getBitDepth();
}
/**
* Gets the target reference frames.
*
* <value>The target reference frames.</value>
*/
public final Integer getTargetRefFrames() {
MediaStream stream = getTargetVideoStream();
return stream == null || !getIsDirectStream() ? null : stream.getRefFrames();
}
/**
* Predicts the audio sample rate that will be in the output stream
*/
public final Float getTargetFramerate() {
MediaStream stream = getTargetVideoStream();
Float tempVar = stream.getAverageFrameRate();
return getMaxFramerate() != null && !getIsDirectStream() ? getMaxFramerate() : stream == null ? null : (tempVar != null) ? tempVar : stream.getRealFrameRate();
}
/**
* Predicts the audio sample rate that will be in the output stream
*/
public final Double getTargetVideoLevel() {
MediaStream stream = getTargetVideoStream();
return getVideoLevel() != null && !getIsDirectStream() ? getVideoLevel() : stream == null ? null : stream.getLevel();
}
/**
* Predicts the audio sample rate that will be in the output stream
*/
public final Integer getTargetPacketLength() {
MediaStream stream = getTargetVideoStream();
return !getIsDirectStream() ? null : stream == null ? null : stream.getPacketLength();
}
/**
* Predicts the audio sample rate that will be in the output stream
*/
public final String getTargetVideoProfile() {
MediaStream stream = getTargetVideoStream();
return !Utils.isEmpty(getVideoProfile()) && !getIsDirectStream() ? getVideoProfile() : stream == null ? null : stream.getProfile();
}
/**
* Gets the target video codec tag.
*
* <value>The target video codec tag.</value>
*/
public final String getTargetVideoCodecTag() {
MediaStream stream = getTargetVideoStream();
return !getIsDirectStream() ? null : stream == null ? null : stream.getCodecTag();
}
/**
* Predicts the audio bitrate that will be in the output stream
*/
public final Integer getTargetAudioBitrate() {
MediaStream stream = getTargetAudioStream();
return getAudioBitrate() != null && !getIsDirectStream() ? getAudioBitrate() : stream == null ? null : stream.getBitRate();
}
/**
* Predicts the audio channels that will be in the output stream
*/
public final Integer getTargetAudioChannels() {
MediaStream stream = getTargetAudioStream();
Integer streamChannels = stream == null ? null : stream.getChannels();
if (getMaxAudioChannels() != null && !getIsDirectStream()) {
if (streamChannels != null) {
return Math.min(getMaxAudioChannels(), streamChannels);
}
return getMaxAudioChannels();
}
return streamChannels;
}
/**
* Predicts the audio codec that will be in the output stream
*/
public final String getTargetAudioCodec() {
MediaStream stream = getTargetAudioStream();
String inputCodec = stream == null ? null : stream.getCodec();
if (getIsDirectStream()) {
return inputCodec;
}
for (String codec : getAudioCodecs()) {
if (codec.equalsIgnoreCase(inputCodec)) {
return codec;
}
}
return getAudioCodecs().length == 0 ? null : getAudioCodecs()[0];
}
/**
* Predicts the audio channels that will be in the output stream
*/
public final Long getTargetSize() {
if (getIsDirectStream()) {
return getMediaSource().getSize();
}
if (getRunTimeTicks() != null) {
Integer totalBitrate = getTargetTotalBitrate();
double totalSeconds = getRunTimeTicks();
// Convert to ms
totalSeconds /= 10000;
// Convert to seconds
totalSeconds /= 1000;
return totalBitrate != null ? java.lang.Math.round(totalBitrate * totalSeconds) : (Long) null;
}
return null;
}
public final Integer getTargetVideoBitrate() {
MediaStream stream = getTargetVideoStream();
return getVideoBitrate() != null && !getIsDirectStream() ? getVideoBitrate() : stream == null ? null : stream.getBitRate();
}
public final TransportStreamTimestamp getTargetTimestamp() {
TransportStreamTimestamp defaultValue = ContainerTypes.M2TS.equalsIgnoreCase(getContainer()) ? TransportStreamTimestamp.Valid : TransportStreamTimestamp.None;
TransportStreamTimestamp tempVar = getMediaSource().getTimestamp();
return !getIsDirectStream() ? defaultValue : getMediaSource() == null ? defaultValue : (tempVar != null) ? tempVar : TransportStreamTimestamp.None;
}
public final Integer getTargetTotalBitrate() {
Integer tempVar = getTargetAudioBitrate();
Integer tempVar2 = getTargetVideoBitrate();
return ((tempVar != null) ? tempVar : 0) + ((tempVar2 != null) ? tempVar2 : 0);
}
public final Boolean getIsTargetAnamorphic() {
if (getIsDirectStream()) {
return getTargetVideoStream() == null ? null : getTargetVideoStream().getIsAnamorphic();
}
return false;
}
public final Integer getTargetWidth() {
MediaStream videoStream = getTargetVideoStream();
if (videoStream != null && videoStream.getWidth() != null && videoStream.getHeight() != null) {
ImageSize tempVar = new ImageSize();
tempVar.setWidth(videoStream.getWidth());
tempVar.setHeight(videoStream.getHeight());
ImageSize size = tempVar.clone();
Double maxWidth = getMaxWidth() != null ? (double) getMaxWidth() : (Double) null;
Double maxHeight = getMaxHeight() != null ? (double) getMaxHeight() : (Double) null;
ImageSize newSize = DrawingUtils.Resize(size.clone(), null, null, maxWidth, maxHeight);
return (int) newSize.getWidth();
}
return getMaxWidth();
}
public final Integer getTargetHeight() {
MediaStream videoStream = getTargetVideoStream();
if (videoStream != null && videoStream.getWidth() != null && videoStream.getHeight() != null) {
ImageSize tempVar = new ImageSize();
tempVar.setWidth(videoStream.getWidth());
tempVar.setHeight(videoStream.getHeight());
ImageSize size = tempVar.clone();
Double maxWidth = getMaxWidth() != null ? (double) getMaxWidth() : (Double) null;
Double maxHeight = getMaxHeight() != null ? (double) getMaxHeight() : (Double) null;
ImageSize newSize = DrawingUtils.Resize(size.clone(), null, null, maxWidth, maxHeight);
return (int) newSize.getHeight();
}
return getMaxHeight();
}
public final Integer getTargetVideoStreamCount() {
if (getIsDirectStream()) {
return GetMediaStreamCount(MediaStreamType.Video, Integer.MAX_VALUE);
}
return GetMediaStreamCount(MediaStreamType.Video, 1);
}
public final Integer getTargetAudioStreamCount() {
if (getIsDirectStream()) {
return GetMediaStreamCount(MediaStreamType.Audio, Integer.MAX_VALUE);
}
return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
private Integer GetMediaStreamCount(MediaStreamType type, int limit) {
Integer count = getMediaSource().GetStreamCount(type);
if (count != null) {
count = Math.min(count, limit);
}
return count;
}
public final ArrayList<MediaStream> GetSelectableAudioStreams() {
return GetSelectableStreams(MediaStreamType.Audio);
}
public final ArrayList<MediaStream> GetSelectableSubtitleStreams() {
return GetSelectableStreams(MediaStreamType.Subtitle);
}
public final ArrayList<MediaStream> GetSelectableStreams(MediaStreamType type) {
ArrayList<MediaStream> list = new ArrayList<MediaStream>();

View File

@@ -1,20 +0,0 @@
package org.jellyfin.androidtv.data.compat;
import java.util.ArrayList;
import java.util.Collections;
@Deprecated
public class StreamInfoSorter
{
public static ArrayList<StreamInfo> SortMediaSources(ArrayList<StreamInfo> streams, Integer maxBitrate)
{
ChainedComparator<StreamInfo> comparator = new ChainedComparator<>(
new StreamInfoSorterComparator(0),
new StreamInfoSorterComparator(1),
new StreamInfoSorterComparator(2)
);
Collections.sort(streams, comparator);
return streams;
}
}

View File

@@ -1,47 +0,0 @@
package org.jellyfin.androidtv.data.compat;
import org.jellyfin.apiclient.model.mediainfo.MediaProtocol;
import org.jellyfin.apiclient.model.session.PlayMethod;
@Deprecated
public class StreamInfoSorterComparator extends BaseStreamInfoSorter {
private int level;
public StreamInfoSorterComparator(int level) {
this.level = level;
}
@Override
protected int getValue(StreamInfo info) {
switch (level) {
case 0: {
// Nothing beats direct playing a file
if (info.getPlayMethod() == PlayMethod.DirectPlay && info.getMediaSource().getProtocol() == MediaProtocol.File) {
return 0;
}
return 1;
}
case 1: {
switch (info.getPlayMethod()) {
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
case DirectStream:
case DirectPlay:
return 0;
default:
return 1;
}
}
case 2: {
switch (info.getMediaSource().getProtocol()) {
case File:
return 0;
default:
return 1;
}
}
}
throw new IllegalArgumentException("Unrecognized level");
}
}

View File

@@ -14,16 +14,6 @@ public class SubtitleStreamInfo {
Url = value;
}
private String Language;
public final String getLanguage() {
return Language;
}
public final void setLanguage(String value) {
Language = value;
}
private String Name;
public final String getName() {
@@ -34,16 +24,6 @@ public class SubtitleStreamInfo {
Name = value;
}
private boolean IsForced;
public final boolean getIsForced() {
return IsForced;
}
public final void setIsForced(boolean value) {
IsForced = value;
}
private String Format;
public final String getFormat() {
@@ -83,14 +63,4 @@ public class SubtitleStreamInfo {
public final void setDeliveryMethod(SubtitleDeliveryMethod value) {
DeliveryMethod = value;
}
private boolean IsExternalUrl;
public final boolean getIsExternalUrl() {
return IsExternalUrl;
}
public final void setIsExternalUrl(boolean value) {
IsExternalUrl = value;
}
}

View File

@@ -1,11 +1,10 @@
package org.jellyfin.androidtv.data.eventhandling;
import static org.koin.java.KoinJavaComponent.get;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import org.jellyfin.androidtv.R;
@@ -32,6 +31,7 @@ import org.jellyfin.apiclient.model.session.MessageCommand;
import org.jellyfin.apiclient.model.session.PlayRequest;
import org.jellyfin.apiclient.model.session.PlaystateRequest;
import org.jellyfin.apiclient.model.session.SessionInfoDto;
import org.koin.java.KoinJavaComponent;
import java.util.Arrays;
@@ -40,10 +40,14 @@ import timber.log.Timber;
public class TvApiEventListener extends ApiEventListener {
private final DataRefreshService dataRefreshService;
private final MediaManager mediaManager;
private final Handler mainThreadHandler;
public TvApiEventListener(DataRefreshService dataRefreshService, MediaManager mediaManager) {
this.dataRefreshService = dataRefreshService;
this.mediaManager = mediaManager;
//handler to interact with the players (exoplayer doesn't allow access from another thread)
mainThreadHandler = new Handler(Looper.getMainLooper());
}
@Override
@@ -75,57 +79,53 @@ public class TvApiEventListener extends ApiEventListener {
@Override
public void onPlaystateCommand(ApiClient client, PlaystateRequest command) {
PlaybackController playbackController = TvApp.getApplication().getPlaybackController();
Timber.d("caught playstate command: %s", command.getCommand());
switch (command.getCommand()) {
case Stop:
if (mediaManager.isPlayingAudio())
mediaManager.stopAudio();
if (mediaManager.getIsAudioPlayerInitialized())
mainThreadHandler.post(() -> mediaManager.stopAudio(true));
else {
Activity currentActivity = TvApp.getApplication().getCurrentActivity();
if(playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager())
mainThreadHandler.post(() -> playbackController.endPlayback());
if(currentActivity instanceof PlaybackOverlayActivity)
currentActivity.finish();
}
break;
case Pause:
if (mediaManager.isPlayingAudio())
mediaManager.pauseAudio();
else if(playbackController != null)
playbackController.playPause();
break;
case Unpause:
if (mediaManager.hasAudioQueueItems())
mediaManager.resumeAudio();
else if(playbackController != null)
playbackController.playPause();
case PlayPause:
if (mediaManager.getIsAudioPlayerInitialized())
mainThreadHandler.post(() -> mediaManager.playPauseAudio());
else if(playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager())
mainThreadHandler.post(() -> playbackController.playPause());
break;
case NextTrack:
if (mediaManager.hasAudioQueueItems())
mediaManager.nextAudioItem();
else if(playbackController != null)
playbackController.next();
if (mediaManager.getIsAudioPlayerInitialized() && mediaManager.hasAudioQueueItems())
mainThreadHandler.post(() -> mediaManager.nextAudioItem());
else if(playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager())
mainThreadHandler.post(() -> playbackController.next());
break;
case PreviousTrack:
if (mediaManager.hasAudioQueueItems())
mediaManager.prevAudioItem();
else if(playbackController != null)
playbackController.prev();
if (mediaManager.getIsAudioPlayerInitialized() && mediaManager.hasAudioQueueItems())
mainThreadHandler.post(() -> mediaManager.prevAudioItem());
else if(playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager())
mainThreadHandler.post(() -> playbackController.prev());
break;
case Seek:
if (playbackController == null) break;
long pos = command.getSeekPositionTicks() / 10000;
playbackController.seek(pos);
if(playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager()) {
long pos = command.getSeekPositionTicks() / 10000;
mainThreadHandler.post(() -> playbackController.seek(pos));
}
break;
case Rewind:
if (playbackController == null) break;
playbackController.skip(-11000);
if (playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager())
mainThreadHandler.post(() -> playbackController.skip(-11000));
break;
case FastForward:
if (playbackController == null) break;
playbackController.skip(30000);
if (playbackController != null && mediaManager.hasVideoQueueItems() && playbackController.hasInitializedVideoManager())
mainThreadHandler.post(() -> playbackController.skip(30000));
break;
}
}
@@ -133,77 +133,82 @@ public class TvApiEventListener extends ApiEventListener {
@Override
public void onBrowseCommand(ApiClient client, BrowseRequest command) {
Timber.d("Browse command received");
if (TvApp.getApplication().getCurrentActivity() == null || videoPlaying()) {
Timber.i("Command ignored due to no activity or playback in progress");
return;
}
client.GetItemAsync(command.getItemId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
//Create a rowItem and pass to our handler
ItemLauncher.launch(new BaseRowItem(0, response), null, -1, TvApp.getApplication().getCurrentActivity(), true);
}
});
}
if (Utils.isEmpty(command.getItemId())) return;
private boolean videoPlaying() {
return (TvApp.getApplication().getPlaybackController() != null && (TvApp.getApplication().getPlaybackController().isPlaying() || TvApp.getApplication().getPlaybackController().isPaused()));
mainThreadHandler.post(() -> {
if (TvApp.getApplication().getCurrentActivity() == null ||
(TvApp.getApplication().getPlaybackController() != null && (TvApp.getApplication().getPlaybackController().isPlaying() || TvApp.getApplication().getPlaybackController().isPaused()))) {
Timber.i("Command ignored due to no activity or playback in progress");
return;
}
client.GetItemAsync(command.getItemId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
//Create a rowItem and pass to our handler
ItemLauncher.launch(new BaseRowItem(0, response), null, -1, TvApp.getApplication().getCurrentActivity(), true);
}
});
});
}
@Override
public void onPlayCommand(ApiClient client, PlayRequest command) {
if (videoPlaying()) {
TvApp.getApplication().getCurrentActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Utils.showToast(TvApp.getApplication().getCurrentActivity(), TvApp.getApplication().getString(R.string.msg_remote_already_playing));
}
});
return;
}
if (command.getItemIds().length > 1) {
Timber.i("Playing multiple items by remote request");
if (TvApp.getApplication().getCurrentActivity() == null) {
Timber.e("No current activity. Cannot play");
mainThreadHandler.post(() -> {
if (TvApp.getApplication().getPlaybackController() != null && (TvApp.getApplication().getPlaybackController().isPlaying() || TvApp.getApplication().getPlaybackController().isPaused())) {
TvApp.getApplication().getCurrentActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Utils.showToast(TvApp.getApplication().getCurrentActivity(), TvApp.getApplication().getString(R.string.msg_remote_already_playing));
}
});
return;
}
StdItemQuery query = new StdItemQuery(new ItemFields[]{
ItemFields.MediaSources,
ItemFields.ChildCount
});
query.setIds(command.getItemIds());
get(ApiClient.class).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
PlaybackLauncher playbackLauncher = get(PlaybackLauncher.class);
if (playbackLauncher.interceptPlayRequest(TvApp.getApplication(), response.getItems()[0])) return;
//peek at first item to see what type it is
switch (response.getItems()[0].getMediaType()) {
case "Video":
Class activity = get(PlaybackLauncher.class).getPlaybackActivityClass(response.getItems()[0].getBaseItemType());
mediaManager.setCurrentVideoQueue(Arrays.asList(response.getItems()));
Intent intent = new Intent(TvApp.getApplication().getCurrentActivity(), activity);
TvApp.getApplication().getCurrentActivity().startActivity(intent);
break;
case "Audio":
mediaManager.playNow(Arrays.asList(response.getItems()));
break;
if (command.getItemIds().length > 1) {
Timber.i("Playing multiple items by remote request");
if (TvApp.getApplication().getCurrentActivity() == null) {
Timber.e("No current activity. Cannot play");
return;
}
int startIndex = command.getStartIndex() == null ? 0 : command.getStartIndex().intValue();
int startPosition = command.getStartPositionTicks() == null || command.getStartPositionTicks().longValue() == 0 ? 0 : Long.valueOf(command.getStartPositionTicks() / 10000L).intValue();
Timber.d("got queue start index: %s position %s", startIndex, startPosition);
StdItemQuery query = new StdItemQuery(new ItemFields[]{
ItemFields.MediaSources,
ItemFields.ChildCount
});
query.setIds(command.getItemIds());
KoinJavaComponent.<ApiClient>get(ApiClient.class).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
PlaybackLauncher playbackLauncher = KoinJavaComponent.<PlaybackLauncher>get(PlaybackLauncher.class);
if (playbackLauncher.interceptPlayRequest(TvApp.getApplication(), response.getItems()[0])) return;
//peek at first item to see what type it is
switch (response.getItems()[0].getMediaType()) {
case "Video":
Class activity = KoinJavaComponent.<PlaybackLauncher>get(PlaybackLauncher.class).getPlaybackActivityClass(response.getItems()[0].getBaseItemType());
mediaManager.setCurrentVideoQueue(Arrays.asList(response.getItems()));
Intent intent = new Intent(TvApp.getApplication().getCurrentActivity(), activity);
intent.putExtra("Position", startPosition);
TvApp.getApplication().getCurrentActivity().startActivity(intent);
break;
case "Audio":
mediaManager.playNow(TvApp.getApplication().getCurrentActivity(), Arrays.asList(response.getItems()), startIndex, false);
break;
}
}
}
}
});
});
} else {
if (command.getItemIds().length > 0) {
Timber.i("Playing single item by remote request");
Context context = TvApp.getApplication().getCurrentActivity() != null ? TvApp.getApplication().getCurrentActivity() : TvApp.getApplication();
PlaybackHelper.retrieveAndPlay(command.getItemIds()[0], false, command.getStartPositionTicks() != null ? command.getStartPositionTicks() : 0, context);
} else {
if (command.getItemIds().length > 0) {
Timber.i("Playing single item by remote request");
Context context = TvApp.getApplication().getCurrentActivity() != null ? TvApp.getApplication().getCurrentActivity() : TvApp.getApplication();
PlaybackHelper.retrieveAndPlay(command.getItemIds()[0], false, command.getStartPositionTicks(), context);
}
}
}
});
}
@Override

View File

@@ -1,10 +1,10 @@
package org.jellyfin.androidtv.data.model;
import org.jellyfin.apiclient.model.querying.ItemFilter;
import java.util.ArrayList;
import java.util.List;
import org.jellyfin.apiclient.model.querying.ItemFilter;
public class FilterOptions {
private boolean favoriteOnly;
private boolean unwatchedOnly;

View File

@@ -1,12 +0,0 @@
package org.jellyfin.androidtv.data.model;
public class LiveTvPrefs {
public String channelOrder = "DatePlayed";
public boolean showNewIndicator;
public boolean showRepeatIndicator;
public boolean showPremiereIndicator = true;
public boolean favsAtTop = true;
public boolean showLiveIndicator = true;
public boolean showHDIndicator;
public boolean colorCodeGuide;
}

View File

@@ -1,7 +1,6 @@
package org.jellyfin.androidtv.data.querying;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemQuery;

View File

@@ -0,0 +1,38 @@
package org.jellyfin.androidtv.data.repository
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import org.jellyfin.apiclient.model.entities.CollectionType
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.BaseItemDto
interface UserViewsRepository {
val views: LiveData<Collection<BaseItemDto>>
fun isSupported(collectionType: String?): Boolean
fun allowViewSelection(collectionType: String?): Boolean
}
class UserViewsRepositoryImpl(
private val api: ApiClient,
) : UserViewsRepository {
override val views: LiveData<Collection<BaseItemDto>> = liveData {
val views by api.userViewsApi.getUserViews()
val filteredViews = views.items
.orEmpty()
.filter { isSupported(it.collectionType) }
emit(filteredViews)
}
override fun isSupported(collectionType: String?) = collectionType !in unsupportedCollectionTypes
override fun allowViewSelection(collectionType: String?) = collectionType != CollectionType.Music
private companion object {
private val unsupportedCollectionTypes = arrayOf(
CollectionType.Books,
CollectionType.Games,
CollectionType.Folders
)
}
}

View File

@@ -17,16 +17,24 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.add
import androidx.window.WindowManager
import androidx.window.layout.WindowMetricsCalculator
import com.bumptech.glide.Glide
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.apiclient.interaction.ApiClient
import org.jellyfin.apiclient.model.dto.BaseItemDto
import org.jellyfin.apiclient.model.dto.ImageOptions
import org.jellyfin.apiclient.model.entities.ImageType
import org.jellyfin.apiclient.model.search.SearchHint
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.serializer.toUUID
import timber.log.Timber
import java.util.concurrent.ExecutionException
@@ -37,7 +45,7 @@ class BackgroundService(
) {
companion object {
const val TRANSITION_DURATION = 400L // 0.4 seconds
const val SLIDESHOW_DURATION = 10000L // 10 seconds
const val SLIDESHOW_DURATION = 30000L // 30 seconds
const val UPDATE_INTERVAL = 500L // 0.5 seconds
val FRAGMENT_TAG = BackgroundServiceFragment::class.qualifiedName!!
}
@@ -99,8 +107,8 @@ class BackgroundService(
backgroundDrawable.setDrawableByLayerId(R.id.background_static, windowBackground)
// Store size of window manager for this activity
windowSize = WindowManager(activity).getCurrentWindowMetrics().bounds.let {
Size(it.right, it.bottom)
windowSize = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity).let {
Size(it.bounds.width(), it.bounds.height())
}
// Add a fragment to the activity to automatically set the background on resume
@@ -122,11 +130,12 @@ class BackgroundService(
if (itemId == null || isNullOrEmpty()) return emptyList()
return mapIndexed { index, tag ->
apiClient.GetImageUrl(itemId, ImageOptions().apply {
imageType = ImageType.Backdrop
setImageIndex(index)
setTag(tag)
})
apiClient.imageApi.getItemImageUrl(
itemId = itemId.toUUID(),
imageType = ImageType.BACKDROP,
tag = tag,
imageIndex = index,
)
}
}
@@ -155,11 +164,13 @@ class BackgroundService(
return clearBackgrounds()
// Manually grab the backdrop URL
val options = ImageOptions()
options.imageType = ImageType.Backdrop
options.tag = searchHint.backdropImageTag
val backdropUrls = setOfNotNull(searchHint.backdropImageItemId?.let { apiClient.GetImageUrl(it, options) })
val backdropUrls = setOfNotNull(searchHint.backdropImageItemId?.let { itemId ->
apiClient.imageApi.getItemImageUrl(
itemId = itemId.toUUID(),
imageType = ImageType.BACKDROP,
tag = searchHint.backdropImageTag,
)
})
loadBackgrounds(backdropUrls)
}

View File

@@ -8,7 +8,7 @@ import org.koin.dsl.bind
import org.koin.dsl.module
val activityLifecycleCallbacksModule = module {
single { AuthenticatedUserCallbacks() } bind Application.ActivityLifecycleCallbacks::class
single { AuthenticatedUserCallbacks(get()) } bind Application.ActivityLifecycleCallbacks::class
single { AppThemeCallbacks(get()) } bind Application.ActivityLifecycleCallbacks::class
single { CurrentActivityCallbacks() } bind Application.ActivityLifecycleCallbacks::class
}

View File

@@ -6,18 +6,21 @@ import org.jellyfin.androidtv.auth.ServerRepository
import org.jellyfin.androidtv.auth.ServerRepositoryImpl
import org.jellyfin.androidtv.data.eventhandling.TvApiEventListener
import org.jellyfin.androidtv.data.model.DataRefreshService
import org.jellyfin.androidtv.data.repository.UserViewsRepository
import org.jellyfin.androidtv.data.repository.UserViewsRepositoryImpl
import org.jellyfin.androidtv.data.service.BackgroundService
import org.jellyfin.androidtv.ui.playback.MediaManager
import org.jellyfin.androidtv.ui.playback.nextup.NextUpViewModel
import org.jellyfin.androidtv.ui.startup.LoginViewModel
import org.jellyfin.androidtv.util.sdk.legacy
import org.jellyfin.apiclient.AppInfo
import org.jellyfin.apiclient.android
import org.jellyfin.apiclient.interaction.AndroidDevice
import org.jellyfin.apiclient.interaction.device.IDevice
import org.jellyfin.apiclient.logging.AndroidLogger
import org.jellyfin.apiclient.serialization.GsonJsonSerializer
import org.jellyfin.sdk.android
import org.jellyfin.sdk.android.androidDevice
import org.jellyfin.sdk.createJellyfin
import org.jellyfin.sdk.model.ClientInfo
import org.jellyfin.sdk.model.DeviceInfo
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
@@ -26,18 +29,20 @@ import org.koin.dsl.module
import org.jellyfin.apiclient.Jellyfin as JellyfinApiClient
import org.jellyfin.sdk.Jellyfin as JellyfinSdk
val defaultDeviceInfo = named("defaultDeviceInfo")
val userApiClient = named("userApiClient")
val systemApiClient = named("systemApiClient")
val appModule = module {
// New SDK
single(defaultDeviceInfo) { androidDevice(get()) }
single {
JellyfinSdk {
// Set deviceInfo and discovery provider
android(androidContext())
createJellyfin {
context = androidContext()
// Add client info
clientInfo = ClientInfo("Android TV", BuildConfig.VERSION_NAME)
// FIXME (SDK 1.2.0) deviceInfo = get(defaultDeviceInfo)
}
}
@@ -62,13 +67,9 @@ val appModule = module {
}
}
single<IDevice> {
AndroidDevice.fromContext(androidApplication())
}
single {
get<JellyfinApiClient>().createApi(
device = get(),
device = get<DeviceInfo>(defaultDeviceInfo).legacy(),
eventListener = TvApiEventListener(get(), get())
)
}
@@ -76,15 +77,15 @@ val appModule = module {
// Non API related
single { MediaManager() }
single { DataRefreshService() }
factory { WorkManager.getInstance(androidContext()) }
single<ServerRepository> { ServerRepositoryImpl(get(), get(), get(), get()) }
single<UserViewsRepository> { UserViewsRepositoryImpl(get(userApiClient)) }
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { NextUpViewModel(get(), get(), get()) }
viewModel { NextUpViewModel(get(), get(), get(), get(), get()) }
single { BackgroundService(get(), get(), get()) }
single { BackgroundService(get(), get(userApiClient), get()) }
}

View File

@@ -2,7 +2,14 @@ package org.jellyfin.androidtv.di
import androidx.core.content.getSystemService
import org.jellyfin.androidtv.JellyfinApplication
import org.jellyfin.androidtv.auth.*
import org.jellyfin.androidtv.auth.AccountManagerHelper
import org.jellyfin.androidtv.auth.ApiBinder
import org.jellyfin.androidtv.auth.AuthenticationRepository
import org.jellyfin.androidtv.auth.AuthenticationRepositoryImpl
import org.jellyfin.androidtv.auth.AuthenticationStore
import org.jellyfin.androidtv.auth.LegacyAccountMigration
import org.jellyfin.androidtv.auth.SessionRepository
import org.jellyfin.androidtv.auth.SessionRepositoryImpl
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
@@ -11,11 +18,11 @@ val authModule = module {
single { AuthenticationStore(androidContext()) }
single { AccountManagerHelper(androidContext().getSystemService()!!) }
single<AuthenticationRepository> {
AuthenticationRepositoryImpl(get(), get(), get(), get(), get(), get(userApiClient), get())
AuthenticationRepositoryImpl(get(), get(), get(), get(), get(userApiClient), get(), get(defaultDeviceInfo))
}
single<SessionRepository> {
SessionRepositoryImpl(get(), get(), get(), get(), get(userApiClient), get(systemApiClient))
SessionRepositoryImpl(get(), get(), get(), get(), get(userApiClient), get(systemApiClient), get(), get(defaultDeviceInfo))
}
single { LegacyAccountMigration(androidContext(), get(), get(), get()) }
single { ApiBinder(androidApplication() as JellyfinApplication, get(), get(), get()) }
single { ApiBinder(androidApplication() as JellyfinApplication, get(), get()) }
}

View File

@@ -0,0 +1,26 @@
package org.jellyfin.androidtv.di
import android.content.Context
import androidx.startup.Initializer
import org.jellyfin.androidtv.LogInitializer
import org.koin.android.ext.koin.androidContext
import org.koin.core.KoinApplication
import org.koin.core.context.startKoin
class KoinInitializer : Initializer<KoinApplication> {
override fun create(context: Context): KoinApplication = startKoin {
androidContext(context)
modules(
appModule,
authModule,
activityLifecycleCallbacksModule,
playbackModule,
preferenceModule,
utilsModule
)
}
override fun dependencies() = listOf(LogInitializer::class.java)
}

View File

@@ -5,17 +5,11 @@ import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.ui.playback.GarbagePlaybackLauncher
import org.jellyfin.androidtv.ui.playback.PlaybackManager
import org.jellyfin.androidtv.ui.playback.RewritePlaybackLauncher
import org.jellyfin.apiclient.interaction.AndroidDevice
import org.jellyfin.apiclient.logging.AndroidLogger
import org.koin.android.ext.koin.androidApplication
import org.koin.dsl.module
val playbackModule = module {
single {
PlaybackManager(
AndroidDevice.fromContext(androidApplication()),
AndroidLogger("PlaybackManager")
)
PlaybackManager(get(userApiClient))
}
factory {

View File

@@ -1,12 +1,19 @@
package org.jellyfin.androidtv.di
import org.jellyfin.androidtv.preference.AuthenticationPreferences
import org.jellyfin.androidtv.preference.LiveTvPreferences
import org.jellyfin.androidtv.preference.PreferencesRepository
import org.jellyfin.androidtv.preference.SystemPreferences
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.UserSettingPreferences
import org.koin.android.ext.koin.androidApplication
import org.koin.dsl.module
val preferenceModule = module {
single { PreferencesRepository(get(userApiClient), get(), get()) }
single { LiveTvPreferences(get(userApiClient)) }
single { UserSettingPreferences(get(userApiClient)) }
single { AuthenticationPreferences(androidApplication()) }
single { UserPreferences(androidApplication()) }
single { SystemPreferences(androidApplication()) }

View File

@@ -1,8 +1,10 @@
package org.jellyfin.androidtv.di
import org.jellyfin.androidtv.util.AutoBitrate
import org.jellyfin.androidtv.util.ImageHelper
import org.koin.dsl.module
val utilsModule = module {
single { AutoBitrate(get(userApiClient)) }
single { ImageHelper(get(userApiClient)) }
}

View File

@@ -8,27 +8,42 @@ import android.os.Build
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import androidx.tvprovider.media.tv.*
import androidx.tvprovider.media.tv.Channel
import androidx.tvprovider.media.tv.ChannelLogoUtils
import androidx.tvprovider.media.tv.PreviewProgram
import androidx.tvprovider.media.tv.TvContractCompat
import androidx.tvprovider.media.tv.TvContractCompat.WatchNextPrograms
import androidx.tvprovider.media.tv.WatchNextProgram
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.data.repository.UserViewsRepository
import org.jellyfin.androidtv.di.systemApiClient
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.ui.itemhandling.ItemRowAdapter
import org.jellyfin.androidtv.ui.startup.StartupActivity
import org.jellyfin.androidtv.util.ImageUtils
import org.jellyfin.androidtv.util.dp
import org.jellyfin.androidtv.util.sdk.isUsable
import org.jellyfin.sdk.api.client.KtorClient
import org.jellyfin.sdk.api.operations.ImageApi
import org.jellyfin.sdk.api.operations.TvShowsApi
import org.jellyfin.sdk.api.operations.UserViewsApi
import org.jellyfin.sdk.model.api.*
import org.jellyfin.apiclient.model.dto.BaseItemType
import org.jellyfin.apiclient.model.entities.MediaType
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.exception.TimeoutException
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import org.koin.core.component.KoinApiExtension
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
import java.time.ZoneOffset
/**
@@ -36,7 +51,6 @@ import java.time.ZoneOffset
*
* More info: https://developer.android.com/training/tv/discovery/recommendations-channel.
*/
@KoinApiExtension
class LeanbackChannelWorker(
private val context: Context,
workerParams: WorkerParameters,
@@ -50,11 +64,9 @@ class LeanbackChannelWorker(
const val PERIODIC_UPDATE_REQUEST_NAME = "LeanbackChannelPeriodicUpdateRequest"
}
private val apiClient by inject<KtorClient>(systemApiClient)
private val userViewsApi by lazy { UserViewsApi(apiClient) }
private val imageApi by lazy { ImageApi(apiClient) }
private val tvShowsApi by lazy { TvShowsApi(apiClient) }
private val api by inject<ApiClient>(systemApiClient)
private val userPreferences by inject<UserPreferences>()
private val userViewsRepository by inject<UserViewsRepository>()
/**
* Check if the app can use Leanback features and is API level 26 or higher.
@@ -72,10 +84,10 @@ class LeanbackChannelWorker(
// Fail when not supported
!isSupported -> Result.failure()
// Retry later if no authenticated user is found
!apiClient.isUsable -> Result.retry()
else -> {
!api.isUsable -> Result.retry()
else -> try {
// Get next up episodes
val nextUpItems = getNextUpItems()
val (resumeItems, nextUpItems) = getNextUpItems()
// Delete current items from the My Media and Next Up channels
context.contentResolver.delete(TvContractCompat.PreviewPrograms.CONTENT_URI, null, null)
@@ -83,10 +95,18 @@ class LeanbackChannelWorker(
// Update various channels
updateMyMedia()
updateNextUp(nextUpItems)
updateWatchNext(nextUpItems)
updateWatchNext(resumeItems + nextUpItems)
// Success!
Result.success()
} catch (err: TimeoutException) {
Timber.w(err, "Server unreachable, trying again later")
Result.retry()
} catch (err: ApiClientException) {
Timber.e(err, "SDK error, trying again later")
Result.retry()
}
}
@@ -95,7 +115,7 @@ class LeanbackChannelWorker(
* update or create the channel. The [name] parameter is used to store the id and should be
* unique.
*/
private fun getChannelUri(name: String, settings: Channel): Uri {
private fun getChannelUri(name: String, settings: Channel, default: Boolean = false): Uri {
val store = context.getSharedPreferences("leanback_channels", Context.MODE_PRIVATE)
val uri = if (store.contains(name)) {
@@ -107,16 +127,16 @@ class LeanbackChannelWorker(
// Create new channel and save uri
context.contentResolver.insert(TvContractCompat.Channels.CONTENT_URI, settings.toContentValues())!!.also { uri ->
store.edit().putString(name, uri.toString()).apply()
if (default) {
// Set as default row to display (we can request one row to automatically be added to the home screen)
TvContractCompat.requestChannelBrowsable(context, ContentUris.parseId(uri))
}
}
// Set as default row to display (we can request one row to automatically be added to the home screen)
// Should be enabled when we add a row that we want to display by default
// TvContractCompat.requestChannelBrowsable(application, ContentUris.parseId(uri))
}
// Update logo
ResourcesCompat.getDrawable(context.resources, R.drawable.app_icon, null)?.let {
ChannelLogoUtils.storeChannelLogo(context, ContentUris.parseId(uri), it.toBitmap(80.dp, 80.dp))
ChannelLogoUtils.storeChannelLogo(context, ContentUris.parseId(uri), it.toBitmap(80.dp(context), 80.dp(context)))
}
return uri
@@ -125,23 +145,24 @@ class LeanbackChannelWorker(
/**
* Updates the "my media" row with current media libraries.
*/
@Suppress("RestrictedApi")
private suspend fun updateMyMedia() {
// Get channel
val channelUri = getChannelUri("my_media", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_my_media))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build())
.build(), default = true)
val response by userViewsApi.getUserViews(includeHidden = false)
val response by api.userViewsApi.getUserViews(includeHidden = false)
// Add new items
val items = response.items
.orEmpty()
.filterNot { it.collectionType in ItemRowAdapter.ignoredCollectionTypes }
.filter { userViewsRepository.isSupported(it.collectionType) }
.map { item ->
val imageUri = if (item.imageTags?.contains(ImageType.PRIMARY) == true)
imageApi.getItemImageUrl(item.id, ImageType.PRIMARY).toUri()
api.imageApi.getItemImageUrl(item.id, ImageType.PRIMARY).toUri()
else
ImageUtils.getResourceUrl(context, R.drawable.tile_land_tv).toUri()
@@ -167,14 +188,14 @@ class LeanbackChannelWorker(
*/
private fun BaseItemDto.getPosterArtImageUrl(preferParentThumb: Boolean): Uri = when {
(preferParentThumb || imageTags?.contains(ImageType.PRIMARY) != true)
&& parentThumbItemId?.toUUIDOrNull() != null -> imageApi.getItemImageUrl(
&& parentThumbItemId?.toUUIDOrNull() != null -> api.imageApi.getItemImageUrl(
itemId = parentThumbItemId!!.toUUIDOrNull()!!,
imageType = ImageType.THUMB,
format = ImageFormat.WEBP,
width = 512,
height = 288
)
else -> imageApi.getItemImageUrl(
else -> api.imageApi.getItemImageUrl(
itemId = id,
imageType = ImageType.PRIMARY,
format = ImageFormat.WEBP,
@@ -184,22 +205,42 @@ class LeanbackChannelWorker(
}.let(Uri::parse)
/**
* Gets the next up episodes or returns null.
* Gets the resume and next up episodes. The returned pair contains two lists:
* 1. resume items
* 2. next up items
*/
private suspend fun getNextUpItems(): BaseItemDtoQueryResult? {
return tvShowsApi.getNextUp(
userId = apiClient.userId,
imageTypeLimit = 1,
limit = 10,
fields = listOf(ItemFields.DATE_CREATED)
).content
private suspend fun getNextUpItems(): Pair<List<BaseItemDto>, List<BaseItemDto>> = withContext(Dispatchers.IO) {
val resume = async {
api.itemsApi.getResumeItems(
fields = listOf(ItemFields.DATE_CREATED),
imageTypeLimit = 1,
limit = 10,
mediaTypes = listOf(MediaType.Video)
).content.items.orEmpty().filter { item ->
// Movies are not supported right now
item.type.equals(BaseItemType.Episode.toString(), true)
}
}
val nextup = async {
api.tvShowsApi.getNextUp(
userId = api.userId,
imageTypeLimit = 1,
limit = 10,
fields = listOf(ItemFields.DATE_CREATED),
).content.items.orEmpty()
}
// Concat
Pair(resume.await(), nextup.await())
}
/**
* Updates the "next up" row with current episodes. Uses the [nextUpItems] parameter to store
* items returned by a NextUpQuery().
*/
private suspend fun updateNextUp(nextUpItems: BaseItemDtoQueryResult?) {
@Suppress("RestrictedApi")
private fun updateNextUp(nextUpItems: List<BaseItemDto>) {
val preferParentThumb = userPreferences[UserPreferences.seriesThumbnailsEnabled]
// Get channel
@@ -210,7 +251,7 @@ class LeanbackChannelWorker(
.build())
// Add new items
nextUpItems?.items?.map { item ->
nextUpItems.map { item ->
val imageUri = item.getPosterArtImageUrl(preferParentThumb)
val seasonString = item.parentIndexNumber?.toString().orEmpty()
@@ -235,7 +276,7 @@ class LeanbackChannelWorker(
})
.build()
.toContentValues()
}?.let { context.contentResolver.bulkInsert(TvContractCompat.PreviewPrograms.CONTENT_URI, it.toTypedArray()) }
}.let { context.contentResolver.bulkInsert(TvContractCompat.PreviewPrograms.CONTENT_URI, it.toTypedArray()) }
}
/**
@@ -243,22 +284,21 @@ class LeanbackChannelWorker(
* or other types of media. Uses the [nextUpItems] parameter to store items returned by a
* NextUpQuery().
*/
private suspend fun updateWatchNext(nextUpItems: BaseItemDtoQueryResult?) {
private fun updateWatchNext(nextUpItems: List<BaseItemDto>) {
// Delete current items
context.contentResolver.delete(WatchNextPrograms.CONTENT_URI, null, null)
// Add new items
nextUpItems?.items?.let { items ->
context.contentResolver.bulkInsert(
WatchNextPrograms.CONTENT_URI,
items.map { item -> getBaseItemAsWatchNextProgram(item).toContentValues() }.toTypedArray()
)
}
context.contentResolver.bulkInsert(
WatchNextPrograms.CONTENT_URI,
nextUpItems.map { item -> getBaseItemAsWatchNextProgram(item).toContentValues() }.toTypedArray()
)
}
/**
* Convert [BaseItemDto] to [WatchNextProgram]. Assumes the item type is "episode".
*/
@Suppress("RestrictedApi")
private fun getBaseItemAsWatchNextProgram(item: BaseItemDto) = WatchNextProgram.Builder().apply {
val preferParentThumb = userPreferences[UserPreferences.seriesThumbnailsEnabled]
@@ -272,25 +312,25 @@ class LeanbackChannelWorker(
setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
// Use date created or fallback to current time if unavailable
setLastEngagementTimeUtcMillis(item.dateCreated?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
?: System.currentTimeMillis())
var engagement = item.dateCreated
when {
// User has started playing the episode
item.userData?.playbackPositionTicks ?: 0 > 0 -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
setLastPlaybackPositionMillis((item.userData!!.playbackPositionTicks / TICKS_IN_MILLISECOND).toInt())
// Use last played date to prioritze
engagement = item.userData?.lastPlayedDate
}
// First episode of the season
item.indexNumber == 1 -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEW)
}
item.indexNumber == 1 -> setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEW)
// Default
else -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEXT)
}
else -> setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEXT)
}
setLastEngagementTimeUtcMillis(engagement?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
?: System.currentTimeMillis())
// Episode runtime has been determined
item.runTimeTicks?.let { runTimeTicks ->
setDurationMillis((runTimeTicks / TICKS_IN_MILLISECOND).toInt())

View File

@@ -0,0 +1,134 @@
package org.jellyfin.androidtv.integration
import android.app.SearchManager
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Intent
import android.content.UriMatcher
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.provider.BaseColumns
import kotlinx.coroutines.runBlocking
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.di.systemApiClient
import org.jellyfin.androidtv.util.ImageUtils
import org.jellyfin.androidtv.util.sdk.isUsable
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemFields
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
import java.time.ZoneId
class MediaContentProvider : ContentProvider(), KoinComponent {
companion object {
private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.content"
private const val SUGGEST_PATH = "suggestions"
private const val SEARCH_SUGGEST = 1
private const val TICKS_IN_MILLISECOND = 10000
private const val DEFAULT_LIMIT = 10
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, "$SUGGEST_PATH/${SearchManager.SUGGEST_URI_PATH_QUERY}", SEARCH_SUGGEST)
addURI(AUTHORITY, "$SUGGEST_PATH/${SearchManager.SUGGEST_URI_PATH_QUERY}/*", SEARCH_SUGGEST)
}
}
private val api by inject<ApiClient>(systemApiClient)
override fun onCreate(): Boolean = api.isUsable
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
Timber.d("Query: %s", uri)
when (uriMatcher.match(uri)) {
SEARCH_SUGGEST -> {
val query = selectionArgs?.firstOrNull() ?: uri.lastPathSegment ?: return null
Timber.d("Search query: $query")
val limit = uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT)?.toIntOrNull()
?: DEFAULT_LIMIT
return getSuggestions(query, limit)
}
else -> throw IllegalArgumentException("Unknown Uri: $uri")
}
}
/**
* Gets the resumable items or returns null
*/
private suspend fun searchItems(query: String, limit: Int): BaseItemDtoQueryResult? = try {
val items by api.itemsApi.getItemsByUserId(
searchTerm = query,
recursive = true,
limit = limit,
fields = setOf(ItemFields.TAGLINES)
)
items
} catch (err: ApiClientException) {
Timber.e(err, "Unable to query API for search results")
null
}
private fun getSuggestions(query: String, limit: Int) = runBlocking {
val searchResult = searchItems(query, limit)
if (searchResult != null) Timber.d("Query resulted in %d items", searchResult.totalRecordCount)
val columns = arrayOf(
BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_DURATION,
SearchManager.SUGGEST_COLUMN_IS_LIVE,
SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT,
SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR,
SearchManager.SUGGEST_COLUMN_QUERY,
SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
)
MatrixCursor(columns).also { cursor ->
searchResult?.items?.forEach { item ->
// Note: Image loading might fail if the image is served over HTTP
val imageUri = if (item.imageTags?.contains(ImageType.PRIMARY) == true)
api.imageApi.getItemImageUrl(item.id, ImageType.PRIMARY)
else
ImageUtils.getResourceUrl(context, R.drawable.tile_land_tv)
cursor.newRow().apply {
add(BaseColumns._ID, item.id)
add(SearchManager.SUGGEST_COLUMN_DURATION, item.runTimeTicks?.run { div(TICKS_IN_MILLISECOND) })
add(SearchManager.SUGGEST_COLUMN_IS_LIVE, if (item.isLive == true) 1 else 0)
val lastAccess = item.userData?.lastPlayedDate?.atZone(ZoneId.systemDefault())?.toEpochSecond()
add(SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT, lastAccess)
add(SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR, item.premiereDate?.year)
add(SearchManager.SUGGEST_COLUMN_QUERY, item.name)
add(SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE, imageUri)
add(SearchManager.SUGGEST_COLUMN_TEXT_1, item.name)
add(SearchManager.SUGGEST_COLUMN_TEXT_2, item.taglines?.firstOrNull())
add(SearchManager.SUGGEST_COLUMN_INTENT_ACTION, Intent.ACTION_VIEW)
add(SearchManager.SUGGEST_COLUMN_INTENT_DATA, item.id)
}
}
}
}
override fun getType(p0: Uri): String = SearchManager.SUGGEST_MIME_TYPE
override fun insert(p0: Uri, p1: ContentValues?): Uri? = null
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int = 0
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int = 0
}

View File

@@ -0,0 +1,9 @@
package org.jellyfin.androidtv.integration.dream
import android.graphics.drawable.Drawable
import org.jellyfin.sdk.model.api.BaseItemDto
data class LibraryDreamItem(
val baseItem: BaseItemDto,
val background: Drawable,
)

View File

@@ -0,0 +1,34 @@
package org.jellyfin.androidtv.integration.dream
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout
import org.jellyfin.androidtv.databinding.ViewDreamLibraryItemBinding
import org.jellyfin.androidtv.util.KenBurnsTransitionGenerator
class LibraryDreamItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
val binding = ViewDreamLibraryItemBinding.inflate(LayoutInflater.from(context), this, true)
private val kenburnsTransitionGenerator = KenBurnsTransitionGenerator(
LibraryDreamService.UPDATE_DELAY,
AccelerateDecelerateInterpolator()
)
init {
binding.background.setTransitionGenerator(kenburnsTransitionGenerator)
}
fun setItem(item: LibraryDreamItem) {
kenburnsTransitionGenerator.reset()
binding.background.setImageDrawable(item.background)
binding.title.text = item.baseItem.name
}
}

View File

@@ -0,0 +1,142 @@
package org.jellyfin.androidtv.integration.dream
import android.graphics.drawable.Drawable
import android.service.dreams.DreamService
import android.view.LayoutInflater
import androidx.core.view.isVisible
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.databinding.DreamLibraryBinding
import org.jellyfin.androidtv.di.systemApiClient
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.ExecutionException
/**
* An Android [DreamService] (screensaver) that shows TV series and movies from all libraries.
* Use `adb shell am start -n "com.android.systemui/.Somnambulator"` to start after changing the
* default screensaver in the device settings.
*/
class LibraryDreamService : DreamService() {
companion object {
const val INITIAL_DELAY = 2_000L // 2s
const val UPDATE_DELAY = 30_000L // 30s
const val TRANSITION_DURATION = 1_000L // 1s
}
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val api: ApiClient by inject(systemApiClient)
private val userPreferences: UserPreferences by inject()
private lateinit var binding: DreamLibraryBinding
override fun onAttachedToWindow() {
super.onAttachedToWindow()
isInteractive = false
isFullscreen = true
binding = DreamLibraryBinding.inflate(LayoutInflater.from(window.context), null, false)
binding.clock.isVisible = when (userPreferences[UserPreferences.clockBehavior]) {
ClockBehavior.ALWAYS, ClockBehavior.IN_MENUS -> true
else -> false
}
setContentView(binding.root)
}
override fun onDreamingStarted() {
super.onDreamingStarted()
scope.launch {
delay(INITIAL_DELAY)
while (isActive) {
update()
delay(UPDATE_DELAY)
}
}
}
private suspend fun getRandomItem(): BaseItemDto? = try {
val response by api.itemsApi.getItemsByUserId(
includeItemTypes = listOf("Movie", "Series"),
recursive = true,
sortBy = listOf("Random"),
limit = 1,
imageTypes = listOf(ImageType.BACKDROP)
)
response.items?.firstOrNull()
} catch (err: ApiClientException) {
Timber.e(err)
null
}
private suspend fun loadBackdrop(item: BaseItemDto): Drawable? {
val tag = item.backdropImageTags?.randomOrNull()
?: item.imageTags?.get(ImageType.BACKDROP)
val url = api.imageApi.getItemImageUrl(
itemId = item.id,
imageType = ImageType.BACKDROP,
tag = tag,
format = ImageFormat.WEBP,
)
return withContext(Dispatchers.IO) {
// Sometimes the backdrop doesn't exist
// make sure to return a null value in those cases
try {
Glide.with(binding.root)
.load(url)
.submit()
.get()
} catch (err: ExecutionException) {
Timber.e(err)
null
}
}
}
private suspend fun update() {
val item = getRandomItem()
val background = item?.let { loadBackdrop(it) }
withContext(Dispatchers.Main) {
if (item == null || background == null) {
binding.itemSwitcher.hideAllViews()
} else with(binding.itemSwitcher) {
getNextView<LibraryDreamItemView>().setItem(
item = LibraryDreamItem(
baseItem = item,
background = background,
)
)
showNextView()
}
}
}
override fun onDreamingStopped() {
super.onDreamingStopped()
scope.cancel()
}
}

View File

@@ -0,0 +1,54 @@
package org.jellyfin.androidtv.preference
import kotlinx.coroutines.runBlocking
interface AsyncPreferenceStore : PreferenceStore {
val shouldUpdate: Boolean
/**
* Save values to store.
*/
suspend fun commit(): Boolean
/**
* Update values from store.
*/
suspend fun update(): Boolean
/**
* Modify the preferences in store and [commit] afterwards. Automatically calls [update] if
* [shouldUpdate] is true. Use `this` keyword to access preferences.
*
* ```kotlin
* store.transaction {
* // get
* val value = this[Preference.x]
* // set
* this[Preference.x] = value
* // get default
* getDefaultValue(Preference.x)
* // set default
* reset(Preference.x)
* // delete
* delete(Preference.x)
* }
* ```
*/
suspend fun transaction(body: AsyncPreferenceStore.() -> Unit): Boolean {
if (shouldUpdate) update()
body()
return commit()
}
/**
* Compatability with old Java classes.
*/
fun updateBlocking() = runBlocking { update() }
/**
* Compatability with old Java classes.
*/
fun commitBlocking() = runBlocking { commit() }
}

View File

@@ -0,0 +1,129 @@
package org.jellyfin.androidtv.preference
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi
import org.jellyfin.sdk.model.api.DisplayPreferencesDto
import org.jellyfin.sdk.model.api.ScrollDirection
import org.jellyfin.sdk.model.api.SortOrder
import timber.log.Timber
abstract class DisplayPreferencesStore(
protected var displayPreferencesId: String,
protected var app: String = "jellyfin-androidtv",
private val api: ApiClient,
) : AsyncPreferenceStore {
private var displayPreferencesDto: DisplayPreferencesDto? = null
private var cachedPreferences: MutableMap<String, String?> = mutableMapOf()
override val shouldUpdate: Boolean
get() = displayPreferencesDto == null
override suspend fun commit(): Boolean {
if (displayPreferencesDto == null) return false
try {
api.displayPreferencesApi.updateDisplayPreferences(
displayPreferencesId = displayPreferencesId,
client = app,
data = displayPreferencesDto!!.copy(
customPrefs = cachedPreferences
)
)
} catch (err: ApiClientException) {
Timber.e(err, "Unable to save displaypreferences. (displayPreferencesId=$displayPreferencesId, app=$app)")
return false
}
return true
}
/**
* Clear local copy of display preferences and require an update for new modifications.
*/
fun clearCache(): Boolean {
if (displayPreferencesDto == null) return false
displayPreferencesDto = null
cachedPreferences.clear()
return true
}
override suspend fun update(): Boolean {
try {
val result by api.displayPreferencesApi.getDisplayPreferences(
displayPreferencesId = displayPreferencesId,
client = app
)
displayPreferencesDto = result
cachedPreferences = result.customPrefs.toMutableMap()
return true
} catch (err: ApiClientException) {
Timber.e(err, "Unable to retrieve displaypreferences. (displayPreferencesId=$displayPreferencesId, app=$app)")
if (displayPreferencesDto == null) {
Timber.i("Creating an empty DisplayPreferencesDto for next commit.")
displayPreferencesDto = DisplayPreferencesDto.empty()
}
return false
}
}
@Suppress("UNCHECKED_CAST")
override fun <T : Preference<V>, V : Any> get(preference: T) = when (preference.type) {
Int::class -> cachedPreferences[preference.key]?.toIntOrNull() ?: preference.defaultValue
Long::class -> cachedPreferences[preference.key]?.toLongOrNull() ?: preference.defaultValue
Boolean::class -> cachedPreferences[preference.key]?.toBooleanStrictOrNull()
?: preference.defaultValue
String::class -> cachedPreferences[preference.key] ?: preference.defaultValue
else -> throw IllegalArgumentException("${preference.type.simpleName} type is not supported")
} as V
override fun <T : Preference<V>, V : Any> set(preference: T, value: V) = when (preference.type) {
Int::class -> cachedPreferences[preference.key] = (value as Int).toString()
Long::class -> cachedPreferences[preference.key] = (value as Long).toString()
Boolean::class -> cachedPreferences[preference.key] = (value as Boolean).toString()
String::class -> cachedPreferences[preference.key] = (value as String).toString()
Enum::class -> cachedPreferences[preference.key] = value.toString()
else -> throw IllegalArgumentException("${preference.type.simpleName} type is not supported")
}
override fun <T : Preference<V>, V : Enum<V>> get(preference: T): V {
val stringValue = cachedPreferences[preference.key]
return if (stringValue.isNullOrBlank()) preference.defaultValue
else preference.type.java.enumConstants?.find {
(it is PreferenceEnum && it.serializedName == stringValue) || it.name == stringValue
} ?: preference.defaultValue
}
override fun <T : Preference<V>, V : Enum<V>> set(preference: T, value: V) {
cachedPreferences[preference.key] = when (value) {
is PreferenceEnum -> value.serializedName
else -> value.toString()
}
}
override fun <T : Preference<V>, V : Any> delete(preference: T) {
cachedPreferences.remove(preference.key)
}
/**
* Create an empty [DisplayPreferencesDto] with default values.
*/
private fun DisplayPreferencesDto.Companion.empty() = DisplayPreferencesDto(
primaryImageHeight = 0,
primaryImageWidth = 0,
customPrefs = emptyMap(),
rememberIndexing = false,
scrollDirection = ScrollDirection.HORIZONTAL,
rememberSorting = false,
showBackdrop = false,
showSidebar = false,
sortOrder = SortOrder.ASCENDING
)
}

View File

@@ -0,0 +1,30 @@
package org.jellyfin.androidtv.preference
import org.jellyfin.androidtv.constant.GridDirection
import org.jellyfin.androidtv.constant.ImageType
import org.jellyfin.androidtv.constant.PosterSize
import org.jellyfin.apiclient.model.entities.SortOrder
import org.jellyfin.sdk.api.client.ApiClient
class LibraryPreferences(
displayPreferencesId: String,
api: ApiClient,
) : DisplayPreferencesStore(
displayPreferencesId = displayPreferencesId,
api = api,
) {
companion object {
val posterSize = Preference.enum("PosterSize", PosterSize.AUTO)
val imageType = Preference.enum("ImageType", ImageType.DEFAULT)
val gridDirection = Preference.enum("GridDirection", GridDirection.HORIZONTAL)
val enableSmartScreen = Preference.boolean("SmartScreen", false)
// Filters
val filterFavoritesOnly = Preference.boolean("FilterFavoritesOnly", false)
val filterUnwatchedOnly = Preference.boolean("FilterUnwatchedOnly", false)
// Item sorting
val sortBy = Preference.string("SortBy", "SortName")
val sortOrder = Preference.enum("SortOrder", SortOrder.Ascending)
}
}

View File

@@ -0,0 +1,21 @@
package org.jellyfin.androidtv.preference
import org.jellyfin.sdk.api.client.ApiClient
class LiveTvPreferences(
api: ApiClient,
) : DisplayPreferencesStore(
displayPreferencesId = "livetv",
api = api,
) {
companion object {
val channelOrder = Preference.string("livetv-channelorder", "DatePlayed")
val colorCodeGuide = Preference.boolean("guide-colorcodedbackgrounds", false)
val favsAtTop = Preference.boolean("livetv-favoritechannelsattop", true)
val showHDIndicator = Preference.boolean("guide-indicator-hd", false)
val showLiveIndicator = Preference.boolean("guide-indicator-live", true)
val showNewIndicator = Preference.boolean("guide-indicator-new", false)
val showPremiereIndicator = Preference.boolean("guide-indicator-premiere", true)
val showRepeatIndicator = Preference.boolean("guide-indicator-repeat", false)
}
}

View File

@@ -0,0 +1,5 @@
package org.jellyfin.androidtv.preference
interface PreferenceEnum {
val serializedName: String
}

View File

@@ -13,10 +13,14 @@ interface PreferenceStore {
operator fun <T : Preference<V>, V : Enum<V>> set(preference: T, value: V)
// store.getDefaultValue(Preference.x)
fun <T : Preference<V>, V : Any> getDefaultValue(preference: T): V
fun <T : Preference<V>, V : Any> getDefaultValue(preference: T): V {
return preference.defaultValue
}
// store.reset(Preference.x)
fun <T : Preference<V>, V : Any> reset(preference: T)
fun <T : Preference<V>, V : Any> reset(preference: T) {
this[preference] = getDefaultValue(preference)
}
// store.delete(Preference.x)
fun <T : Preference<V>, V : Any> delete(preference: T)

View File

@@ -0,0 +1,35 @@
package org.jellyfin.androidtv.preference
import org.jellyfin.sdk.api.client.ApiClient
import kotlin.collections.set
/**
* Repository to access special preference stores.
*/
class PreferencesRepository(
private val api: ApiClient,
private val liveTvPreferences: LiveTvPreferences,
private val userSettingPreferences: UserSettingPreferences,
) {
private val libraryPreferences = mutableMapOf<String, LibraryPreferences>()
fun getLibraryPreferences(preferencesId: String): LibraryPreferences {
val store = libraryPreferences[preferencesId] ?: LibraryPreferences(preferencesId, api)
libraryPreferences[preferencesId] = store
// FIXME: Make [getLibraryPreferences] suspended when usages are converted to Kotlin
if (store.shouldUpdate) store.updateBlocking()
return store
}
suspend fun onSessionChanged() {
// Note: Do not run parallel as the server can't deal with that
// Relevant server issue: https://github.com/jellyfin/jellyfin/issues/5261
liveTvPreferences.update()
userSettingPreferences.update()
libraryPreferences.clear()
}
}

View File

@@ -1,6 +1,7 @@
package org.jellyfin.androidtv.preference
import android.content.SharedPreferences
import org.jellyfin.androidtv.preference.migrations.MigrationContext
import timber.log.Timber
/**
@@ -38,16 +39,14 @@ abstract class SharedPreferenceStore(
// Getters and setters
// Primitive types
@Suppress("UNCHECKED_CAST")
override operator fun <T : Preference<V>, V : Any> get(preference: T): V {
return when (preference.type) {
Int::class -> sharedPreferences.getInt(preference.key, preference.defaultValue as Int) as V
Long::class -> sharedPreferences.getLong(preference.key, preference.defaultValue as Long) as V
Boolean::class -> sharedPreferences.getBoolean(preference.key, preference.defaultValue as Boolean) as V
String::class -> sharedPreferences.getString(preference.key, preference.defaultValue as String) as V
override operator fun <T : Preference<V>, V : Any> get(preference: T) = when (preference.type) {
Int::class -> sharedPreferences.getInt(preference.key, preference.defaultValue as Int)
Long::class -> sharedPreferences.getLong(preference.key, preference.defaultValue as Long)
Boolean::class -> sharedPreferences.getBoolean(preference.key, preference.defaultValue as Boolean)
String::class -> sharedPreferences.getString(preference.key, preference.defaultValue as String)
else -> throw IllegalArgumentException("${preference.type.simpleName} type is not supported")
}
}
else -> throw IllegalArgumentException("${preference.type.simpleName} type is not supported")
} as V
override operator fun <T : Preference<V>, V : Any> set(preference: T, value: V) = transaction {
when (preference.type) {
@@ -65,57 +64,34 @@ abstract class SharedPreferenceStore(
override operator fun <T : Preference<V>, V : Enum<V>> get(preference: T): V {
val stringValue = sharedPreferences.getString(preference.key, null)
return if (stringValue == null) preference.defaultValue
else preference.type.java.enumConstants?.find { it.name == stringValue }
?: preference.defaultValue
return if (stringValue.isNullOrBlank()) preference.defaultValue
else preference.type.java.enumConstants?.find {
(it is PreferenceEnum && it.serializedName == stringValue) || it.name == stringValue
} ?: preference.defaultValue
}
override operator fun <T : Preference<V>, V : Enum<V>> set(preference: T, value: V) = transaction {
putString(preference.key, value.toString())
override fun <T : Preference<V>, V : Enum<V>> set(preference: T, value: V) = transaction {
putString(preference.key, when (value) {
is PreferenceEnum -> value.serializedName
else -> value.toString()
})
}
// Additional mutations
override fun <T : Preference<V>, V : Any> getDefaultValue(preference: T): V {
return preference.defaultValue
}
override fun <T : Preference<V>, V : Any> reset(preference: T) {
this[preference] = getDefaultValue(preference)
}
override fun <T : Preference<V>, V : Any> delete(preference: T) = transaction {
remove(preference.key)
}
// Migrations
/**
* Migration function to upgrade the preferences in older app versions
*
* Migrations should be added to the `init` block of a store and look like this:
* ```kotlin
* migration(toVersion = 1) {
* // Get a value
* it.getString("example", "default")
* // Set a value
* setString("example", "new value")
* }
*
* @param toVersion The new version to upgrade to
* @param body Actual migrationb code
*/
protected fun migration(toVersion: Int, body: MigrationEditor.(SharedPreferences) -> Unit) {
// Check if migration should be performed
val currentVersion = this[VERSION]
if (currentVersion < toVersion) {
Timber.i("Migrating a preference store from version $currentVersion to $toVersion")
protected fun runMigrations(body: MigrationContext<MigrationEditor, SharedPreferences>.() -> Unit) {
val context = MigrationContext<MigrationEditor, SharedPreferences>()
context.body()
// Create a new editor and execute the migration
transaction {
body(sharedPreferences)
}
this[VERSION] = context.applyMigrations(this[VERSION]) { migration ->
Timber.i("Migrating a preference store to version ${migration.toVersion}")
// Update current store version
this[VERSION] = toVersion
// Create a new transaction and execute the migration
transaction { migration.body(this, sharedPreferences) }
}
}
@@ -123,6 +99,6 @@ abstract class SharedPreferenceStore(
/**
* Version of the preference store. Used for migration.
*/
val VERSION = Preference.int("store_version", 1)
val VERSION = Preference.int("store_version", -1)
}
}

View File

@@ -87,7 +87,7 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
/**
* Preferred video player.
*/
var videoPlayer = Preference.enum("video_player", PreferredVideoPlayer.AUTO)
var videoPlayer = Preference.enum("video_player", PreferredVideoPlayer.EXOPLAYER)
/**
* Enable refresh rate switching when device supports it
@@ -187,40 +187,49 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
* Enable series thumbnails in home screen rows
*/
var seriesThumbnailsEnabled = Preference.boolean("pref_enable_series_thumbnails", true)
/**
* Enable subtitles background
*/
var subtitlesBackgroundEnabled = Preference.boolean("subtitles_background_enabled", true)
/**
* Set default subtitles font size
*/
var defaultSubtitlesSize = Preference.int("subtitles_size", 28)
}
init {
// Migrations
// v0.10.x to v0.11.x: Old migrations
migration(toVersion = 2) {
// Migrate to video player enum
// Note: This is the only time we need to check if the value is not set yet because the version numbers were reset
if (!it.contains("video_player"))
putEnum("video_player", if (it.getBoolean("pref_video_use_external", false)) PreferredVideoPlayer.EXTERNAL else PreferredVideoPlayer.AUTO)
}
// Note: Create a single migration per app version
// Note: Migrations are never executed for fresh installs
runMigrations {
// v0.10.x to v0.11.x
migration(toVersion = 2) {
// Migrate to video player enum
// Note: This is the only time we need to check if the value is not set yet because the version numbers were reset
if (!it.contains("video_player"))
putEnum("video_player", if (it.getBoolean("pref_video_use_external", false)) PreferredVideoPlayer.EXTERNAL else PreferredVideoPlayer.AUTO)
}
// v0.11.x to v0.12.x: Migrates from the old way of storing preferences to the current
migration(toVersion = 3) {
// Migrate to audio behavior enum
putEnum("audio_behavior", if (it.getString("pref_audio_option", "0") == "1") AudioBehavior.DOWNMIX_TO_STEREO else AudioBehavior.DIRECT_STREAM)
// v0.11.x to v0.12.x
migration(toVersion = 5) {
// Migrate to audio behavior enum
putEnum("audio_behavior", if (it.getString("pref_audio_option", "0") == "1") AudioBehavior.DOWNMIX_TO_STEREO else AudioBehavior.DIRECT_STREAM)
// Migrate live tv player to use enum
putEnum("live_tv_video_player",
when {
it.getBoolean("pref_live_tv_use_external", false) -> PreferredVideoPlayer.EXTERNAL
it.getBoolean("pref_enable_vlc_livetv", false) -> PreferredVideoPlayer.VLC
else -> PreferredVideoPlayer.AUTO
})
}
// Migrate live tv player to use enum
putEnum("live_tv_video_player",
when {
it.getBoolean("pref_live_tv_use_external", false) -> PreferredVideoPlayer.EXTERNAL
it.getBoolean("pref_enable_vlc_livetv", false) -> PreferredVideoPlayer.VLC
else -> PreferredVideoPlayer.AUTO
})
// Change audio delay type from long to int
migration(toVersion = 4) {
putInt("libvlc_audio_delay", it.getLong("libvlc_audio_delay", 0).toInt())
}
// Change audio delay type from long to int
putInt("libvlc_audio_delay", it.getLong("libvlc_audio_delay", 0).toInt())
// Disable AC3 (Dolby Digital) on Fire Stick Gen 1 devices
migration(toVersion = 5) {
if (DeviceUtils.isFireTvStickGen1()) putBoolean("pref_bitstream_ac3", false)
// Disable AC3 (Dolby Digital) on Fire Stick Gen 1 devices
if (DeviceUtils.isFireTvStickGen1()) putBoolean("pref_bitstream_ac3", false)
}
}
}
}

View File

@@ -0,0 +1,30 @@
package org.jellyfin.androidtv.preference
import org.jellyfin.androidtv.constant.HomeSectionType
import org.jellyfin.sdk.api.client.ApiClient
class UserSettingPreferences(
api: ApiClient,
) : DisplayPreferencesStore(
displayPreferencesId = "usersettings",
api = api,
app = "emby",
) {
companion object {
val skipBackLength = Preference.int("skipBackLength", 10000)
val skipForwardLength = Preference.int("skipForwardLength", 30000)
val homesection0 = Preference.enum("homesection0", HomeSectionType.LIBRARY_TILES_SMALL)
val homesection1 = Preference.enum("homesection1", HomeSectionType.RESUME)
val homesection2 = Preference.enum("homesection2", HomeSectionType.RESUME_AUDIO)
val homesection3 = Preference.enum("homesection3", HomeSectionType.RESUME_BOOK)
val homesection4 = Preference.enum("homesection4", HomeSectionType.LIVE_TV)
val homesection5 = Preference.enum("homesection5", HomeSectionType.NEXT_UP)
val homesection6 = Preference.enum("homesection6", HomeSectionType.LATEST_MEDIA)
}
val homesections
get() = listOf(homesection0, homesection1, homesection2, homesection3, homesection4, homesection5, homesection6)
.map(::get)
.filterNot { it == HomeSectionType.NONE }
}

View File

@@ -22,3 +22,5 @@ enum class NextUpBehavior {
@EnumDisplayOptions(R.string.lbl_never)
DISABLED
}
const val NEXTUP_TIMER_DISABLED: Int = 0

View File

@@ -0,0 +1,64 @@
package org.jellyfin.androidtv.preference.migrations
import timber.log.Timber
import java.lang.Integer.max
class MigrationContext<E, V> {
private val migrations = mutableListOf<Migration<E, V>>()
private var highVersion: Int = -1
/**
* Migration function to upgrade the preferences from older app versions.
*
* Migrations look like this:
* ```kotlin
* migration(toVersion = 1) {
* // Get a value
* it.getString("example", "default")
* // Set a value
* setString("example", "new value")
* }
*
* @param toVersion The new version to upgrade to
* @param body Actual migration code
*/
inline fun migration(toVersion: Int, noinline body: E.(V) -> Unit) = migration(Migration(toVersion, body))
fun migration(definition: Migration<E, V>) {
if (definition.toVersion > highVersion) highVersion = definition.toVersion
migrations.add(definition)
}
/**
* Apply all defined migrations. Do not call manually.
* When [currentVersion] is -1 all migrations are skipped.
*
* @return The new version of the store. This is the highest "toVersion" number.
*/
fun applyMigrations(currentVersion: Int, executeMigration: (Migration<E, V>) -> Unit): Int {
Timber.i("Requested migration from $currentVersion to $highVersion. Found ${migrations.size} migrations in total.")
return when {
// No migrations
highVersion == -1 -> currentVersion
// All migrations already applied
currentVersion >= highVersion -> currentVersion
// Skip migrations (fresh install)
currentVersion == -1 -> highVersion
// Run migrations
else -> migrations
// Filter out old migrations
.filter { it.toVersion > currentVersion }
// Execute in order
.sortedBy { it.toVersion }
// Call executor
.forEach { executeMigration(it) }
// Return highest version
.let { max(highVersion, currentVersion) }
}
}
data class Migration<E, V>(
val toVersion: Int,
val body: E.(V) -> Unit
)
}

View File

@@ -10,7 +10,7 @@ import android.widget.PopupWindow;
import androidx.annotation.Nullable;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.databinding.AudioDelayPopupBinding;
import org.jellyfin.androidtv.util.Utils;
public class AudioDelayPopup {
@@ -20,19 +20,19 @@ public class AudioDelayPopup {
public AudioDelayPopup(Context context, View anchor, ValueChangedListener<Long> listener) {
LayoutInflater inflater = LayoutInflater.from(context);
View layout = inflater.inflate(R.layout.audio_delay_popup, null);
AudioDelayPopupBinding binding = AudioDelayPopupBinding.inflate(inflater, null, false);
int width = Utils.convertDpToPixel(context, 240);
int height = Utils.convertDpToPixel(context, 130);
mPopup = new PopupWindow(layout, width, height);
mPopup = new PopupWindow(binding.getRoot(), width, height);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mAnchor = anchor;
mDelaySpinner = layout.findViewById(R.id.numberSpinner);
mDelaySpinner = binding.numberSpinner;
mDelaySpinner.setOnChangeListener(listener);
}
@@ -49,10 +49,4 @@ public class AudioDelayPopup {
mDelaySpinner.setValue(value);
mPopup.showAsDropDown(mAnchor, 0, 0, Gravity.END);
}
public void dismiss() {
if (mPopup != null && mPopup.isShowing()) {
mPopup.dismiss();
}
}
}

View File

@@ -14,11 +14,9 @@ import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.androidtv.ui.playback.PlaybackOverlayActivity
import org.jellyfin.androidtv.util.ImageUtils
import org.jellyfin.androidtv.util.getActivity
import org.koin.core.component.KoinApiExtension
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@KoinApiExtension
class ClockUserView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -42,7 +40,7 @@ class ClockUserView @JvmOverloads constructor(
if (currentUser != null && !isInEditMode) {
if (currentUser.primaryImageTag != null) {
Glide.with(context)
.load(ImageUtils.getPrimaryImageUrl(currentUser, get()))
.load(ImageUtils.getPrimaryImageUrl(currentUser))
.placeholder(R.drawable.ic_user)
.centerInside()
.circleCrop()

View File

@@ -5,9 +5,11 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.databinding.NewDetailsOverviewRowBinding;
public class DetailRowView extends FrameLayout {
private NewDetailsOverviewRowBinding binding;
public DetailRowView(Context context) {
super(context);
inflateView(context);
@@ -20,6 +22,10 @@ public class DetailRowView extends FrameLayout {
private void inflateView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.new_details_overview_row, this);
binding = NewDetailsOverviewRowBinding.inflate(inflater, this, true);
}
public NewDetailsOverviewRowBinding getBinding() {
return binding;
}
}

View File

@@ -37,7 +37,7 @@ class ExpandableTextView(context: Context, attrs: AttributeSet?) : AppCompatText
// Show popup
popup.showAtLocation(rootView, Gravity.CENTER, 0, 0)
popup.setOutsideTouchable(true)
popup.isOutsideTouchable = true
// Focus on scroll view
popupContentBinding.scrollContainer.requestFocus()

View File

@@ -0,0 +1,62 @@
package org.jellyfin.androidtv.ui
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.animation.AlphaAnimation
import android.widget.FrameLayout
import androidx.annotation.IntRange
import org.jellyfin.androidtv.integration.dream.LibraryDreamService
class FadeViewSwitcher @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
companion object {
const val VIEW_NONE = -1
}
private var currentView = VIEW_NONE
private val nextViewId
get() = if (currentView >= childCount - 1) 0 else currentView + 1
private val previousViewId
get() = if (currentView <= 0) childCount - 1 else currentView - 1
override fun onViewAdded(child: View?) {
super.onViewAdded(child)
// Hide new views by default
child?.alpha = 0f
}
fun showNextView() = showView(nextViewId)
fun showPreviousView() = showView(previousViewId)
fun hideAllViews() = showView(VIEW_NONE)
fun <V : View> getNextView() = getChildAt(nextViewId) as V
fun <V : View> getCurrentView() = if (currentView == VIEW_NONE) null else currentView as V
fun <V : View> getPreviousView() = getChildAt(previousViewId) as V
fun showView(@IntRange(from = -1) view: Int) {
// TODO: right now crossfades, need to be smarter to hide old view after new view is completely visible
if (currentView != VIEW_NONE) getChildAt(currentView).fadeOut()
if (view != VIEW_NONE) getChildAt(view).fadeIn()
currentView = view
}
private fun View.fadeIn() {
alpha = 1f
startAnimation(AlphaAnimation(0f, 1f).apply {
duration = LibraryDreamService.TRANSITION_DURATION
fillAfter = true
})
}
private fun View.fadeOut() = startAnimation(AlphaAnimation(1f, 0f).apply {
duration = LibraryDreamService.TRANSITION_DURATION
fillAfter = true
})
}

View File

@@ -5,10 +5,11 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.leanback.widget.BaseGridView;
import androidx.leanback.widget.ObjectAdapter;
@@ -20,8 +21,8 @@ import androidx.leanback.widget.RowPresenter;
import androidx.leanback.widget.VerticalGridPresenter;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.data.model.FilterOptions;
import org.jellyfin.androidtv.databinding.HorizontalGridBrowseBinding;
import org.jellyfin.androidtv.ui.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.ui.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.ui.presentation.HorizontalGridPresenter;
@@ -50,25 +51,59 @@ public class GridFragment extends Fragment {
private int mSelectedPosition = -1;
private int mCardHeight;
protected int SMALL_CARD = Utils.convertDpToPixel(TvApp.getApplication(), 116);
protected int MED_CARD = Utils.convertDpToPixel(TvApp.getApplication(), 175);
protected int LARGE_CARD = Utils.convertDpToPixel(TvApp.getApplication(), 210);
protected int SMALL_BANNER = Utils.convertDpToPixel(TvApp.getApplication(), 58);
protected int MED_BANNER = Utils.convertDpToPixel(TvApp.getApplication(), 88);
protected int LARGE_BANNER = Utils.convertDpToPixel(TvApp.getApplication(), 105);
protected int SMALL_CARD;
protected int MED_CARD;
protected int LARGE_CARD;
protected int SMALL_BANNER;
protected int MED_BANNER;
protected int LARGE_BANNER;
protected int SMALL_VERTICAL_POSTER;
protected int MED_VERTICAL_POSTER;
protected int LARGE_VERTICAL_POSTER;
protected int SMALL_VERTICAL_SQUARE;
protected int MED_VERTICAL_SQUARE;
protected int LARGE_VERTICAL_SQUARE;
protected int SMALL_VERTICAL_THUMB;
protected int MED_VERTICAL_THUMB;
protected int LARGE_VERTICAL_THUMB;
protected int SMALL_VERTICAL_BANNER;
protected int MED_VERTICAL_BANNER;
protected int LARGE_VERTICAL_BANNER;
protected int SMALL_VERTICAL_POSTER = Utils.convertDpToPixel(TvApp.getApplication(), 116);
protected int MED_VERTICAL_POSTER = Utils.convertDpToPixel(TvApp.getApplication(), 171);
protected int LARGE_VERTICAL_POSTER = Utils.convertDpToPixel(TvApp.getApplication(), 202);
protected int SMALL_VERTICAL_SQUARE = Utils.convertDpToPixel(TvApp.getApplication(), 114);
protected int MED_VERTICAL_SQUARE = Utils.convertDpToPixel(TvApp.getApplication(), 163);
protected int LARGE_VERTICAL_SQUARE = Utils.convertDpToPixel(TvApp.getApplication(), 206);
protected int SMALL_VERTICAL_THUMB = Utils.convertDpToPixel(TvApp.getApplication(), 116);
protected int MED_VERTICAL_THUMB = Utils.convertDpToPixel(TvApp.getApplication(), 155);
protected int LARGE_VERTICAL_THUMB = Utils.convertDpToPixel(TvApp.getApplication(), 210);
protected int SMALL_VERTICAL_BANNER = Utils.convertDpToPixel(TvApp.getApplication(), 51);
protected int MED_VERTICAL_BANNER = Utils.convertDpToPixel(TvApp.getApplication(), 77);
protected int LARGE_VERTICAL_BANNER = Utils.convertDpToPixel(TvApp.getApplication(), 118);
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SMALL_CARD = Utils.convertDpToPixel(getContext(), 116);
MED_CARD = Utils.convertDpToPixel(getContext(), 175);
LARGE_CARD = Utils.convertDpToPixel(getContext(), 210);
SMALL_BANNER = Utils.convertDpToPixel(getContext(), 58);
MED_BANNER = Utils.convertDpToPixel(getContext(), 88);
LARGE_BANNER = Utils.convertDpToPixel(getContext(), 105);
SMALL_VERTICAL_POSTER = Utils.convertDpToPixel(getContext(), 116);
MED_VERTICAL_POSTER = Utils.convertDpToPixel(getContext(), 171);
LARGE_VERTICAL_POSTER = Utils.convertDpToPixel(getContext(), 202);
SMALL_VERTICAL_SQUARE = Utils.convertDpToPixel(getContext(), 114);
MED_VERTICAL_SQUARE = Utils.convertDpToPixel(getContext(), 163);
LARGE_VERTICAL_SQUARE = Utils.convertDpToPixel(getContext(), 206);
SMALL_VERTICAL_THUMB = Utils.convertDpToPixel(getContext(), 116);
MED_VERTICAL_THUMB = Utils.convertDpToPixel(getContext(), 155);
LARGE_VERTICAL_THUMB = Utils.convertDpToPixel(getContext(), 210);
SMALL_VERTICAL_BANNER = Utils.convertDpToPixel(getContext(), 51);
MED_VERTICAL_BANNER = Utils.convertDpToPixel(getContext(), 77);
LARGE_VERTICAL_BANNER = Utils.convertDpToPixel(getContext(), 118);
sortOptions = new HashMap<>();
{
sortOptions.put(0, new SortOption(getString(R.string.lbl_name), "SortName", SortOrder.Ascending));
sortOptions.put(1, new SortOption(getString(R.string.lbl_date_added), "DateCreated,SortName", SortOrder.Descending));
sortOptions.put(2, new SortOption(getString(R.string.lbl_premier_date), "PremiereDate,SortName", SortOrder.Descending));
sortOptions.put(3, new SortOption(getString(R.string.lbl_rating), "OfficialRating,SortName", SortOrder.Ascending));
sortOptions.put(4, new SortOption(getString(R.string.lbl_community_rating), "CommunityRating,SortName", SortOrder.Descending));
sortOptions.put(5,new SortOption(getString(R.string.lbl_critic_rating), "CriticRating,SortName", SortOrder.Descending));
sortOptions.put(6, new SortOption(getString(R.string.lbl_last_played), "DatePlayed,SortName", SortOrder.Descending));
};
}
/**
* Sets the grid presenter.
@@ -127,7 +162,7 @@ public class GridFragment extends Fragment {
public void setItem(BaseRowItem item) {
if (item != null) {
mTitleView.setText(item.getFullName(requireContext()));
InfoLayoutHelper.addInfoRow(getActivity(), item, mInfoRow, true, true);
InfoLayoutHelper.addInfoRow(requireContext(), item, mInfoRow, true, true);
} else {
mTitleView.setText("");
mInfoRow.removeAllViews();
@@ -146,16 +181,7 @@ public class GridFragment extends Fragment {
}
}
protected Map<Integer, SortOption> sortOptions = new HashMap<>();
{
sortOptions.put(0, new SortOption(TvApp.getApplication().getString(R.string.lbl_name), "SortName", SortOrder.Ascending));
sortOptions.put(1, new SortOption(TvApp.getApplication().getString(R.string.lbl_date_added), "DateCreated,SortName", SortOrder.Descending));
sortOptions.put(2, new SortOption(TvApp.getApplication().getString(R.string.lbl_premier_date), "PremiereDate,SortName", SortOrder.Descending));
sortOptions.put(3,new SortOption(TvApp.getApplication().getString(R.string.lbl_rating), "OfficialRating,SortName", SortOrder.Ascending));
sortOptions.put(4,new SortOption(TvApp.getApplication().getString(R.string.lbl_community_rating), "CommunityRating,SortName", SortOrder.Descending));
sortOptions.put(5,new SortOption(TvApp.getApplication().getString(R.string.lbl_critic_rating), "CriticRating,SortName", SortOrder.Descending));
sortOptions.put(6,new SortOption(TvApp.getApplication().getString(R.string.lbl_last_played), "DatePlayed,SortName", SortOrder.Descending));
}
protected Map<Integer, SortOption> sortOptions;
protected String getSortFriendlyName(String value) {
return getSortOption(value).name;
@@ -175,20 +201,20 @@ public class GridFragment extends Fragment {
}
public void setStatusText(String folderName) {
String text = TvApp.getApplication().getResources().getString(R.string.lbl_showing) + " ";
String text = getString(R.string.lbl_showing) + " ";
FilterOptions filters = mAdapter.getFilters();
if (filters == null || (!filters.isFavoriteOnly() && !filters.isUnwatchedOnly())) {
text += TvApp.getApplication().getResources().getString(R.string.lbl_all_items);
text += getString(R.string.lbl_all_items);
} else {
text += (filters.isUnwatchedOnly() ? TvApp.getApplication().getResources().getString(R.string.lbl_unwatched) : "") + " " +
(filters.isFavoriteOnly() ? TvApp.getApplication().getResources().getString(R.string.lbl_favorites) : "");
text += (filters.isUnwatchedOnly() ? getString(R.string.lbl_unwatched) : "") + " " +
(filters.isFavoriteOnly() ? getString(R.string.lbl_favorites) : "");
}
if (mAdapter.getStartLetter() != null) {
text += " " + TvApp.getApplication().getResources().getString(R.string.lbl_starting_with) + " " + mAdapter.getStartLetter();
text += " " + getString(R.string.lbl_starting_with) + " " + mAdapter.getStartLetter();
}
text += " " + TvApp.getApplication().getString(R.string.lbl_from) + " '" + folderName + "' " + TvApp.getApplication().getString(R.string.lbl_sorted_by) + " " + getSortFriendlyName(mAdapter.getSortBy());
text += " " + getString(R.string.lbl_from) + " '" + folderName + "' " + getString(R.string.lbl_sorted_by) + " " + getSortFriendlyName(mAdapter.getSortBy());
mStatusText.setText(text);
}
@@ -275,26 +301,25 @@ public class GridFragment extends Fragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.horizontal_grid_browse,
container, false);
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
HorizontalGridBrowseBinding binding = HorizontalGridBrowseBinding.inflate(inflater, container, false);
mTitleView = (TextView) root.findViewById(R.id.title);
mStatusText = (TextView) root.findViewById(R.id.statusText);
mInfoRow = (LinearLayout) root.findViewById(R.id.infoRow);
mToolBar = (LinearLayout) root.findViewById(R.id.toolBar);
mCounter = (TextView) root.findViewById(R.id.counter);
mGridDock = (ViewGroup) root.findViewById(R.id.rowsFragment);
mTitleView = binding.title;
mStatusText = binding.statusText;
mInfoRow = binding.infoRow;
mToolBar = binding.toolBar;
mCounter = binding.counter;
mGridDock = binding.rowsFragment;
// Hide the description because we don't have room for it
((NowPlayingBug)root.findViewById(R.id.npBug)).showDescription(false);
binding.npBug.showDescription(false);
return root;
return binding.getRoot();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
createGrid();
}
@@ -329,16 +354,6 @@ public class GridFragment extends Fragment {
mGridView = null;
}
/**
* Sets the selected item position.
*/
public void setSelectedPosition(int position) {
mSelectedPosition = position;
if(mGridView != null && mGridView.getAdapter() != null) {
mGridView.setSelectedPositionSmooth(position);
}
}
private void updateAdapter() {
if (mGridView != null) {
mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);

View File

@@ -14,36 +14,33 @@ import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.ui.livetv.ILiveTvGuide;
import org.jellyfin.androidtv.ui.livetv.LiveTvGuideActivity;
import org.jellyfin.androidtv.ui.livetv.LiveTvGuide;
import org.jellyfin.androidtv.util.ImageUtils;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.model.livetv.ChannelInfoDto;
import static org.koin.java.KoinJavaComponent.get;
import org.koin.java.KoinJavaComponent;
public class GuideChannelHeader extends RelativeLayout {
private ImageView mChannelImage;
private ImageView mFavImage;
private ChannelInfoDto mChannel;
private Context mContext;
private ILiveTvGuide mTvGuide;
private LiveTvGuide mTvGuide;
public GuideChannelHeader(Context context, ILiveTvGuide tvGuide, ChannelInfoDto channel) {
public GuideChannelHeader(Context context, LiveTvGuide tvGuide, ChannelInfoDto channel) {
super(context);
initComponent(context, tvGuide, channel);
}
private void initComponent(Context context, ILiveTvGuide tvGuide, ChannelInfoDto channel) {
private void initComponent(Context context, LiveTvGuide tvGuide, ChannelInfoDto channel) {
mContext = context;
mChannel = channel;
mTvGuide = tvGuide;
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.channel_header, this, false);
int headerWidth = Utils.convertDpToPixel(context, 160);
v.setLayoutParams(new AbsListView.LayoutParams(headerWidth, LiveTvGuideActivity.ROW_HEIGHT));
v.setLayoutParams(new AbsListView.LayoutParams(headerWidth, Utils.convertDpToPixel(context, 55)));
this.addView(v);
this.setFocusable(true);
((TextView) findViewById(R.id.channelName)).setText(channel.getName());
@@ -60,7 +57,7 @@ public class GuideChannelHeader extends RelativeLayout {
int imageHeight = Utils.convertDpToPixel(mContext, 30);
Glide.with(mContext)
.load(ImageUtils.getPrimaryImageUrl(mChannel, get(ApiClient.class)))
.load(ImageUtils.getPrimaryImageUrl(mChannel, KoinJavaComponent.<ApiClient>get(ApiClient.class)))
.override(imageWidth, imageHeight)
.centerInside()
.into(mChannelImage);

View File

@@ -9,8 +9,9 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.ui.livetv.ILiveTvGuide;
import org.jellyfin.androidtv.ui.livetv.LiveTvGuide;
import org.jellyfin.androidtv.ui.livetv.LiveTvGuideActivity;
import org.jellyfin.androidtv.util.Utils;
public class GuidePagingButton extends RelativeLayout {
@@ -26,7 +27,7 @@ public class GuidePagingButton extends RelativeLayout {
super(context, attrs);
}
public GuidePagingButton(final Activity activity, final ILiveTvGuide guide, int start, String label) {
public GuidePagingButton(final Activity activity, final LiveTvGuide guide, int start, String label) {
super(activity);
us = this;
@@ -34,7 +35,7 @@ public class GuidePagingButton extends RelativeLayout {
setBackgroundResource(R.drawable.gray_gradient);
TextView txtLabel = new TextView(activity);
txtLabel.setHeight(LiveTvGuideActivity.PAGEBUTTON_HEIGHT);
txtLabel.setHeight(Utils.convertDpToPixel(getContext(), 55));
txtLabel.setTextColor(Color.BLACK);
setPadding(100,0,0,0);
txtLabel.setFocusable(true);

View File

@@ -7,20 +7,19 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.data.querying.StdItemQuery;
import org.jellyfin.androidtv.databinding.ItemListBinding;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import org.koin.java.KoinJavaComponent;
import java.util.ArrayList;
import java.util.List;
import static org.koin.java.KoinJavaComponent.get;
public class ItemListView extends FrameLayout {
Context mContext;
LinearLayout mList;
@@ -40,9 +39,9 @@ public class ItemListView extends FrameLayout {
private void inflateView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.item_list, this);
ItemListBinding binding = ItemListBinding.inflate(inflater, this, true);
mContext = context;
mList = findViewById(R.id.songList);
mList = binding.songList;
}
public void setRowSelectedListener(ItemRowView.RowSelectedListener listener) { mRowSelectedListener = listener; }
@@ -88,7 +87,7 @@ public class ItemListView extends FrameLayout {
query.setUserId(TvApp.getApplication().getCurrentUser().getId());
String[] ids = new String[mItemIds.size()];
query.setIds(mItemIds.toArray(ids));
get(ApiClient.class).GetItemsAsync(query, new Response<ItemsResult>() {
KoinJavaComponent.<ApiClient>get(ApiClient.class).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getItems() != null) {

View File

@@ -13,7 +13,7 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.util.TextUtilsKt;
import org.jellyfin.androidtv.databinding.ItemRowBinding;
import org.jellyfin.androidtv.util.TimeUtils;
import org.jellyfin.androidtv.util.apiclient.BaseItemUtils;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
@@ -64,14 +64,14 @@ public class ItemRowView extends FrameLayout {
private void inflateView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.item_row, this);
ItemRowBinding binding = ItemRowBinding.inflate(inflater, this, true);
mContext = context;
mWholeRow = findViewById(R.id.wholeRow);
mIndexNo = findViewById(R.id.indexNo);
mItemName = findViewById(R.id.songName);
mExtraName = findViewById(R.id.artistName);
mRunTime = findViewById(R.id.runTime);
mWatchedMark = findViewById(R.id.watchedMark);
mWholeRow = binding.wholeRow;
mIndexNo = binding.indexNo;
mItemName = binding.songName;
mExtraName = binding.artistName;
mRunTime = binding.runTime;
mWatchedMark = binding.watchedMark;
normalBackground = mWholeRow.getBackground();
setFocusable(true);
}
@@ -121,7 +121,7 @@ public class ItemRowView extends FrameLayout {
public void updateWatched() {
if (mBaseItem == null) return;
if ("Video".equals(mBaseItem.getMediaType()) && mBaseItem.getUserData() != null && mBaseItem.getUserData().getPlayed()) {
mWatchedMark.setText(TextUtilsKt.toHtmlSpanned("&#x2713;"));
mWatchedMark.setText("");
} else {
mWatchedMark.setText("");
}

View File

@@ -1,6 +1,5 @@
package org.jellyfin.androidtv.ui;
import static org.koin.java.KoinJavaComponent.get;
import static org.koin.java.KoinJavaComponent.inject;
import android.app.AlertDialog;
@@ -21,7 +20,7 @@ import androidx.core.content.ContextCompat;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.data.model.DataRefreshService;
import org.jellyfin.androidtv.ui.livetv.ILiveTvGuide;
import org.jellyfin.androidtv.ui.livetv.LiveTvGuide;
import org.jellyfin.androidtv.ui.livetv.TvManager;
import org.jellyfin.androidtv.ui.shared.BaseActivity;
import org.jellyfin.androidtv.util.InfoLayoutHelper;
@@ -34,6 +33,7 @@ import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
import org.jellyfin.apiclient.model.livetv.ChannelInfoDto;
import org.jellyfin.apiclient.model.livetv.SeriesTimerInfoDto;
import org.koin.java.KoinJavaComponent;
import java.util.Date;
@@ -45,7 +45,7 @@ public class LiveProgramDetailPopup {
private BaseItemDto mProgram;
private ProgramGridCell mSelectedProgramView;
private BaseActivity mActivity;
private ILiveTvGuide mTvGuide;
private LiveTvGuide mTvGuide;
private TextView mDTitle;
private TextView mDSummary;
private TextView mDRecordInfo;
@@ -64,7 +64,7 @@ public class LiveProgramDetailPopup {
private Lazy<ApiClient> apiClient = inject(ApiClient.class);
public LiveProgramDetailPopup(BaseActivity activity, ILiveTvGuide tvGuide, int width, EmptyResponse tuneAction) {
public LiveProgramDetailPopup(BaseActivity activity, LiveTvGuide tvGuide, int width, EmptyResponse tuneAction) {
mActivity = activity;
mTvGuide = tvGuide;
mTuneAction = tuneAction;
@@ -361,7 +361,7 @@ public class LiveProgramDetailPopup {
channel.setUserData(response);
fave.setImageDrawable(ContextCompat.getDrawable(mActivity, response.getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart));
mTvGuide.refreshFavorite(channel.getId());
DataRefreshService dataRefreshService = get(DataRefreshService.class);
DataRefreshService dataRefreshService = KoinJavaComponent.<DataRefreshService>get(DataRefreshService.class);
dataRefreshService.setLastFavoriteUpdate(System.currentTimeMillis());
}
});

View File

@@ -1,6 +1,5 @@
package org.jellyfin.androidtv.ui;
import static org.koin.java.KoinJavaComponent.get;
import static org.koin.java.KoinJavaComponent.inject;
import android.content.Context;
@@ -16,14 +15,13 @@ import android.widget.TextView;
import com.bumptech.glide.Glide;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.databinding.NowPlayingBugBinding;
import org.jellyfin.androidtv.ui.playback.AudioEventListener;
import org.jellyfin.androidtv.ui.playback.AudioNowPlayingActivity;
import org.jellyfin.androidtv.ui.playback.MediaManager;
import org.jellyfin.androidtv.ui.playback.PlaybackController;
import org.jellyfin.androidtv.util.ImageUtils;
import org.jellyfin.androidtv.util.TimeUtils;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import kotlin.Lazy;
@@ -49,19 +47,17 @@ public class NowPlayingBug extends FrameLayout {
private void init(Context context) {
this.context = context;
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.now_playing_bug, this, true);
NowPlayingBugBinding binding = NowPlayingBugBinding.inflate(inflater, this, true);
if (!isInEditMode()) {
npIcon = (ImageView)v.findViewById(R.id.npIcon);
npDesc = ((TextView) v.findViewById(R.id.npDesc));
npStatus = ((TextView) v.findViewById(R.id.npStatus));
npIcon = binding.npIcon;
npDesc = binding.npDesc;
npStatus = binding.npStatus;
this.setFocusable(true);
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (TvApp.getApplication().getCurrentActivity() != null) {
Intent np = new Intent(TvApp.getApplication(), AudioNowPlayingActivity.class);
TvApp.getApplication().getCurrentActivity().startActivity(np);
}
Intent np = new Intent(context, AudioNowPlayingActivity.class);
context.startActivity(np);
}
});
}
@@ -81,7 +77,14 @@ public class NowPlayingBug extends FrameLayout {
AudioEventListener listener = new AudioEventListener() {
@Override
public void onPlaybackStateChange(PlaybackController.PlaybackState newState, BaseItemDto currentItem) {
if (newState == PlaybackController.PlaybackState.PLAYING && currentItem != null) setInfo(currentItem);
if (currentItem == null)
return;
if (newState == PlaybackController.PlaybackState.PLAYING) {
setInfo(currentItem);
} else if (newState == PlaybackController.PlaybackState.IDLE && isShown()) {
setStatus(mediaManager.getValue().getCurrentAudioPosition());
}
}
@Override
@@ -128,7 +131,7 @@ public class NowPlayingBug extends FrameLayout {
if (item == null) return;
Glide.with(context)
.load(ImageUtils.getPrimaryImageUrl(item, get(ApiClient.class)))
.load(ImageUtils.getPrimaryImageUrl(item))
.error(R.drawable.ic_album)
.centerInside()
.into(npIcon);

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