Compare commits

..

380 Commits

Author SHA1 Message Date
Bill Thornton
a67c486385 Merge pull request #311 from jellyfin/v0.11.1
v0.11.1
2020-02-06 23:40:00 -05:00
Bill Thornton
8aa0c5a18e Disable all subtitle rendering in exoplayer 2020-02-06 23:23:20 -05:00
Florianisme
4c5eab8d62 Fix wrong last position when media player is closed and progress still reported
(cherry picked from commit 8cd11c888c)
2020-02-06 14:58:43 +01:00
Bill Thornton
5e016b38b0 Fix crash on empty server address 2020-02-06 01:09:41 -05:00
Niels van Velzen
5e4e135239 Fix default home sections not used when the user partially changed them 2020-02-06 00:08:45 -05:00
Andreas
8c1d6c30de Fix displaying ACRA as enabled by default in preferences 2020-02-06 00:08:36 -05:00
Niels van Velzen
ec83ec0540 Update version to 0.11.1 2020-02-06 00:06:50 -05:00
Bill Thornton
32dd0ae3ce Merge pull request #289 from nielsvanvelzen/jitpack-finally-passed
Set apiclient version to 0.5.0
2020-02-04 08:22:52 -05:00
Niels van Velzen
e258ae8113 Set apiclient version to 0.5.0 2020-02-04 10:05:01 +01:00
Bill Thornton
c11d26c887 Merge remote-tracking branch 'upstream/master' into release 2020-02-03 17:06:58 -05:00
Bill Thornton
56d9950898 Merge pull request #286 from nielsvanvelzen/took-us-only-3-months
Update app version
2020-02-03 16:55:48 -05:00
Niels van Velzen
60007bf43d Update app version 2020-02-03 22:40:12 +01:00
Bill Thornton
1af15961e8 Merge pull request #279 from thornbill/remove-strings
Remove removed strings
2020-01-30 22:08:46 -05:00
Bill Thornton
e6f82208d2 Remove removed strings 2020-01-27 10:52:15 -05:00
Alexander Brissman
5cec9f1ef3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (346 of 346 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2020-01-26 14:17:26 -05:00
SaddFox
4cd27fc4a4 Translated using Weblate (Slovenian)
Currently translated at 40.4% (138 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sl/
2020-01-25 10:17:25 -05:00
Niels van Velzen
269a6160b7 Translated using Weblate (Dutch)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nl/
2020-01-25 10:17:25 -05:00
Niels van Velzen
3a077e4d1c Merge pull request #274 from joshuaboniface/weblate-fail
Fix bad rebase merge from Weblate
2020-01-24 20:44:31 +01:00
Joshua M. Boniface
4b0390efd5 Fix bad rebase merge from Weblate 2020-01-23 13:41:42 -05:00
4d1m
41b52d3616 Translated using Weblate (Romanian)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ro/
2020-01-23 13:26:24 -05:00
NunzioArdi
182daa3ae4 Translated using Weblate (French)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2020-01-23 13:26:09 -05:00
nextlooper42
3f31bd349a Translated using Weblate (Slovak)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sk/
2020-01-23 13:25:49 -05:00
SaddFox
ab25bd7d81 Translated using Weblate (Slovenian)
Currently translated at 40.1% (137 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sl/
2020-01-23 13:25:49 -05:00
Slobodan Simić
2c3ae8e6a8 Translated using Weblate (Serbian)
Currently translated at 23.7% (81 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sr/
2020-01-23 13:25:49 -05:00
Z Yang
4d0bad1533 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2020-01-23 13:25:49 -05:00
Alexander Brissman
e50c7f2211 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2020-01-23 13:25:49 -05:00
nextlooper42
128ee79abb Translated using Weblate (Slovak)
Currently translated at 51.8% (177 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sk/
2020-01-23 13:25:23 -05:00
Philmo67
6a6c6c275a Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2020-01-23 13:25:13 -05:00
Slobodan Simić
04dddff75f Added translation using Weblate (Serbian) 2020-01-23 13:24:54 -05:00
Philmo67
b2cb898534 Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2020-01-23 13:24:54 -05:00
Bakumin
1268a72527 Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2020-01-23 13:24:54 -05:00
Florian
49e7c760a2 Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2020-01-23 13:24:54 -05:00
spagnoloG
535602b67c Translated using Weblate (Slovenian)
Currently translated at 31.6% (108 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sl/
2020-01-23 13:24:34 -05:00
tokiedokie
8fee67c2e4 Translated using Weblate (Japanese)
Currently translated at 90.1% (308 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ja/
2020-01-23 13:24:34 -05:00
Bakumin
89a283440b Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2020-01-23 13:24:34 -05:00
Libor Filípek
774bff2b49 Translated using Weblate (Czech)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/cs/
2020-01-23 13:24:34 -05:00
spagnoloG
4d34a30dbf Added translation using Weblate (Slovenian) 2020-01-23 13:24:17 -05:00
shoeflydbm
1320da0797 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2020-01-23 13:24:17 -05:00
shoeflydbm
b171bdd7ea Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2020-01-23 13:24:17 -05:00
Rotekoppen
c1be6543ab Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2020-01-23 13:24:17 -05:00
pcmester
473905239d Translated using Weblate (Hungarian)
Currently translated at 5.3% (18 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/hu/
2020-01-23 13:24:17 -05:00
Travis Longstocking
20af2f4b8e Translated using Weblate (Spanish)
Currently translated at 97.1% (332 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2020-01-23 13:24:17 -05:00
Anders Bengtsson
e8c7809b1e Translated using Weblate (Swedish)
Currently translated at 3.8% (13 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sv/
2020-01-23 13:24:17 -05:00
damnitall
26f96ee332 Translated using Weblate (Portuguese (Portugal))
Currently translated at 73.7% (252 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_PT/
2020-01-23 13:24:17 -05:00
pcmester
dc6d2d9c53 Added translation using Weblate (Hungarian) 2020-01-23 13:24:17 -05:00
DenisDehaty
5e1925c7f1 Translated using Weblate (Slovak)
Currently translated at 9.1% (31 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sk/
2020-01-23 13:24:17 -05:00
jfin
bf57210edd Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.4% (234 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_PT/
2020-01-23 13:24:17 -05:00
jfin
2f7fed9df6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.5% (337 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2020-01-23 13:23:59 -05:00
vimo
ce1b6a2228 Translated using Weblate (Dutch)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nl/
2020-01-23 13:23:59 -05:00
David
9739898bd1 Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2020-01-23 13:23:42 -05:00
Daniel De Jesus
3c20fd688d Translated using Weblate (Spanish (Dominican Republic))
Currently translated at 3.2% (11 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es_DO/
2020-01-23 13:23:42 -05:00
Allan
e9ba3131db Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2020-01-23 13:23:42 -05:00
David
b93552ecfb Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2020-01-23 13:23:42 -05:00
m0d3rnX
7e52bb48b6 Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2020-01-23 13:23:42 -05:00
4d1m
193d526583 Translated using Weblate (Romanian)
Currently translated at 39.5% (135 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ro/
2020-01-23 13:23:10 -05:00
tanto faz
c0100c1c3c Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.7% (334 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2020-01-23 13:23:10 -05:00
Davi Carlos de Lira Ferreira
0ffbed66fc Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.7% (334 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2020-01-23 13:23:10 -05:00
Odd Stråbø
8665c4a39e Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.4% (340 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2020-01-23 13:23:10 -05:00
ridaso
e4db812b82 Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2020-01-23 13:23:10 -05:00
Daniel De Jesus
90d02fa0ed Translated using Weblate (Spanish)
Currently translated at 92.4% (316 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2020-01-23 13:23:10 -05:00
Marcos Sanmartín
940c15511c Translated using Weblate (Spanish)
Currently translated at 92.4% (316 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2020-01-23 13:22:48 -05:00
Daniel De Jesus
78a595ff4c Added translation using Weblate (Spanish (Dominican Republic)) 2020-01-23 13:21:13 -05:00
Niels van Velzen
8a2edbcc0a Merge pull request #272 from thornbill/fix-release
Fix invalid id reference causing lintVitalRelease to fail
2020-01-23 18:57:32 +01:00
Bill Thornton
821a53f016 Fix invalid id reference causing lintVitalRelease to fail 2020-01-23 12:21:00 -05:00
Bill Thornton
46c8d8dcee Merge pull request #271 from thornbill/docker-build
Fix docker build
2020-01-23 10:34:52 -05:00
Bill Thornton
16d55baeb9 Fix docker build 2020-01-23 09:54:19 -05:00
Niels van Velzen
52eec32518 Merge pull request #268 from thornbill/add-live-tv-buttons
Add HomeFragmentRow for live tv buttons
2020-01-23 09:41:20 +01:00
Bill Thornton
055893eb2b Remove unneeded variable definitions 2020-01-23 00:30:53 -05:00
Bill Thornton
93302c34f7 Update null safety in HomeFragmentLiveTVRow 2020-01-22 23:56:19 -05:00
Bill Thornton
1f7f486732 Update comments 2020-01-21 14:39:17 -05:00
Bill Thornton
97ad3ab320 Add HomeFragmentRow for live tv buttons 2020-01-21 14:36:14 -05:00
Bill Thornton
3abc2a0d2c Move home fragments to ui package 2020-01-21 10:57:58 -05:00
Niels van Velzen
9ec29a6f15 Merge pull request #267 from Froghut/search_enhancements
Search enhancements
2020-01-20 20:42:34 +01:00
Froghut
6d46bc1084 fix naming 2020-01-20 19:47:35 +01:00
Froghut
65708a5fc5 cleanup 2020-01-19 19:19:36 +01:00
Froghut
2588e7f1f8 make categories in search always show in the same order and split Series + Episodes 2020-01-19 19:17:59 +01:00
Bill Thornton
21453982a3 Merge pull request #266 from nielsvanvelzen/readme
Add downloads to README
2020-01-18 17:00:20 -05:00
Niels van Velzen
8a8831ef84 Add downloads to README
Also adds additional links and some text about translations (copied from web repo)
2020-01-18 13:17:11 +01:00
Bill Thornton
c02b65a5e9 Merge pull request #263 from thornbill/channel-manager-fix
Fix ChannelManager crash on older API levels
2020-01-16 12:40:18 -05:00
Bill Thornton
e5771f4c95 Fix ChannelManager crash on older API levels 2020-01-16 11:07:57 -05:00
Bill Thornton
bf79e8c337 Merge pull request #244 from nielsvanvelzen/exoplayer2
Upgrade to ExoPlayer 2
2020-01-08 15:42:11 -05:00
Bill Thornton
ba3a95fc3d Merge pull request #249 from nielsvanvelzen/player-preferrence
Add option to choose the preferred media player in settings
2020-01-08 15:34:09 -05:00
Niels van Velzen
35c0801f0d Set default value for useVlc to make sure it is always set according to preferences.
Also removes the forced-enabling of VLC in the error handler for requesting stream info
2020-01-07 22:30:11 +01:00
Niels van Velzen
91e7252025 Make sure to use VLC when preferred, even when transcoding 2020-01-07 22:23:33 +01:00
Bill Thornton
2cd9d9e6d3 Merge pull request #232 from nielsvanvelzen/kotlin-channels
Add new and unfinished episodes to "watch next" section on Android TV
2020-01-07 13:33:17 -05:00
Niels van Velzen
7ae58475e5 Merge branch 'master' into player-preferrence
# Conflicts:
#	app/src/main/java/org/jellyfin/androidtv/TvApp.java
2020-01-07 19:26:22 +01:00
Niels van Velzen
87043fcaf2 Merge branch 'master' into kotlin-channels
# Conflicts:
#	app/build.gradle
2020-01-07 19:10:35 +01:00
Bill Thornton
3738563a73 Merge pull request #239 from nielsvanvelzen/search
Refactor search
2020-01-06 13:24:05 -05:00
Niels van Velzen
859f8d107b Properly release exoplayer. fixes potential memory issue 2020-01-04 15:06:13 +01:00
Niels van Velzen
c411f58c3d Remove old strings from other languages as well 2020-01-03 20:06:25 +01:00
Niels van Velzen
1e275345d4 Merge branch 'master' into player-preferrence
# Conflicts:
#	app/src/main/res/values/strings.xml
2020-01-03 20:04:34 +01:00
Niels van Velzen
c2cc1afac1 Merge branch 'master' into search
# Conflicts:
#	app/build.gradle
2020-01-03 20:01:28 +01:00
Niels van Velzen
f5c87878f6 Fix bad import after merge 2020-01-03 20:00:08 +01:00
Niels van Velzen
35021068c2 Merge branch 'master' into kotlin-channels
# Conflicts:
#	app/src/main/java/org/jellyfin/androidtv/browsing/HomeFragment.java
2020-01-03 19:58:21 +01:00
Niels van Velzen
96c0dc0f87 Remove recommendation code (was unused and not working) 2020-01-03 19:55:50 +01:00
Niels van Velzen
1ca6abdd13 Remove build flavors
Uses the amazon fork for exoplayer for all builds. This removes the need for a separate flavor for google/amazon devices 🎉
2020-01-03 19:36:28 +01:00
Niels van Velzen
5049e7d09f Add missing click listener to text search fragment 2020-01-03 19:27:14 +01:00
Bill Thornton
911f00f8b2 Merge pull request #246 from Florianisme/play-button-focus
Focus resume or otherwise play button by default
2020-01-02 14:59:54 -05:00
Bill Thornton
fbfaa3db2e Merge pull request #210 from nielsvanvelzen/homesections
Add home sections
2020-01-02 14:52:42 -05:00
Bill Thornton
dc1e41a510 Add excluded collection types to latest media home section 2020-01-02 14:42:03 -05:00
Niels van Velzen
290ba7bbb4 Fix exception logging 2020-01-02 14:42:03 -05:00
Niels van Velzen
fa9c5621e4 Always go to user selection screen on "logout" 2020-01-02 14:42:03 -05:00
Niels van Velzen
9d7bb0f232 Fix async loading of user views to fix row ordering 2020-01-02 14:41:17 -05:00
Niels van Velzen
722d766c28 Group items 2020-01-02 14:39:30 -05:00
Niels van Velzen
cd26e12bdc Show user preferred libraries in separate rows as "latest" (wip, async) 2020-01-02 14:39:30 -05:00
Niels van Velzen
45caec1460 Fix signout when auto login is off 2020-01-02 14:39:30 -05:00
Niels van Velzen
9755caa71e Fix minimum SDK issue (getContext() was not supported) 2020-01-02 14:39:30 -05:00
Niels van Velzen
5a16dbc77b Add home sections 2020-01-02 14:39:30 -05:00
Niels van Velzen
3897ebbb55 Remove strings for external player option 2020-01-02 19:14:08 +01:00
Niels van Velzen
e36dd6c968 Add option to choose the preferred media player in settings 2020-01-02 19:13:00 +01:00
Bill Thornton
5c4f213383 Increase max jvm heap size 2020-01-02 12:25:59 -05:00
Niels van Velzen
bf33dc60bd Remove showSearchWithPermission method 2020-01-02 17:50:52 +01:00
Niels van Velzen
038caab680 Disable forced speech disabled activity 2020-01-02 17:50:28 +01:00
Florianisme
344130ca7e Focus resume or otherwise play button by default 2020-01-02 13:52:21 +01:00
Niels van Velzen
1704e3df3d Fix channels updating twice on app start 2020-01-02 00:19:10 -05:00
Niels van Velzen
8ca73dd713 Add support for Android Leanback "Watch Next" feature 2020-01-02 00:19:09 -05:00
Bill Thornton
93a23c69bb Merge pull request #231 from nielsvanvelzen/kotlin
Enable Kotlin support
2020-01-02 00:12:09 -05:00
Joshua M. Boniface
cad39f36cc Merge pull request #228 from nielsvanvelzen/build-file
Rename "build" file to "build.sh"
2020-01-01 23:32:14 -05:00
dkanada
ece9324d32 Merge pull request #243 from thornbill/fix-card-margin
Fix top margin on card title
2020-01-02 13:03:05 +09:00
Niels van Velzen
3c7d9c86a9 Single event listener, fix setZoom, disable controls in XML, fix play after pause 2020-01-01 16:20:50 +01:00
Niels van Velzen
cb27fc6309 Fix audio progress not working 2020-01-01 15:56:53 +01:00
Niels van Velzen
b9cec5294e Update VideoManager to exoplayer 2 2020-01-01 15:34:26 +01:00
Niels van Velzen
1b30d78b1a Use ExoPlayer 2 in MediaManager (audio player) 2020-01-01 15:20:37 +01:00
Niels van Velzen
6771bd2d14 Remove exomedia and upgrade exoplayer to the latest amazon revision 2020-01-01 14:45:34 +01:00
Niels van Velzen
229990b50b Add separate search fragment for speech incapable devices (like Fire TV) 2020-01-01 10:53:48 +01:00
Niels van Velzen
de2081bec2 Use more simple approach for disabling speech 2019-12-31 12:04:31 +01:00
Niels van Velzen
900032efa6 Separate SearchFragment and SearchProvider 2019-12-31 11:30:59 +01:00
Niels van Velzen
71e434517b Disable speech when not supported 2019-12-31 11:24:17 +01:00
Bill Thornton
7e4b454a42 Fix top margin on card title 2019-12-31 02:09:46 -05:00
Bill Thornton
6a91c08b55 Merge pull request #237 from nielsvanvelzen/readable-titles
Make title in cards readable
2019-12-31 01:12:56 -05:00
Niels van Velzen
2c2e738222 Remove no-speech search xml files 2019-12-29 18:38:55 +01:00
Niels van Velzen
91bef5afc6 Refactor SearchFragment 2019-12-29 18:07:09 +01:00
Niels van Velzen
ec83261b43 Remove MySearchFragmentNoSpeech and custom speech permission logic (is now handled by androidx SearchSupportFragment) 2019-12-29 17:43:09 +01:00
Niels van Velzen
a352a32bab Make title in cards scroll and increase the element width 2019-12-29 17:24:13 +01:00
Bill Thornton
25edae1563 Merge pull request #217 from AndreasGB/typestring-to-enum
Replace string usages for BaseItemDto.Type with the EBaseItemType
2019-12-24 08:08:31 -05:00
Niels van Velzen
8efe4d23de Switch to kotlin stdlib for jdk 8 2019-12-24 11:19:04 +01:00
Niels van Velzen
cc767ef1cb Fix crash because the saved instance state can be null 2019-12-24 11:15:21 +01:00
Niels van Velzen
4eb2b9db7a Add .editorconfig for Kotlin files 2019-12-24 11:15:20 +01:00
Niels van Velzen
54d9cba938 Convert MainActivity to kotlin fail to demonstrate Kotlin support 2019-12-24 11:15:20 +01:00
Niels van Velzen
9bfb1c7a8e Rename "build" file to "build.sh" because Gradle needs a "build" directory in the project root 2019-12-24 11:15:19 +01:00
Niels van Velzen
3566785030 Add Kotlin support 2019-12-24 11:15:19 +01:00
Bill Thornton
e23fb21fd2 Merge pull request #200 from thornbill/acra
Add ACRA for crash reporting
2019-12-23 19:07:30 -05:00
Bill Thornton
a5a1b8c9d7 Merge pull request #234 from nielsvanvelzen/remove-unused-resources
Remove unused resources
2019-12-23 17:11:12 -05:00
Niels van Velzen
99b164e240 Remove lbl_select_color from strings.xml (string is removed) 2019-12-23 22:13:11 +01:00
Niels van Velzen
6fff8ff642 Restore legacy theme 2019-12-23 22:07:11 +01:00
Niels van Velzen
d0490d318a Manually remove additional file (integers.xml) and comment in themes.xml for removed legacy theme 2019-12-23 22:07:10 +01:00
Niels van Velzen
35823fb11c Remove unused drawables, layouts, strings, arrays and ids 2019-12-23 22:07:10 +01:00
Bill Thornton
b42285f838 Merge pull request #233 from nielsvanvelzen/deprecation-fixes
Deprecation fixes
2019-12-23 15:06:50 -05:00
Niels van Velzen
d65843fb6d Convert remaining activities to FragmentActivity 2019-12-23 19:20:20 +01:00
ridaso
4cf20c4ede Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-12-23 13:14:38 -05:00
Andreas Ackermann
d0272cc3fb Undo version bump, point at apiclient library master-SNAPSHOT instead 2019-12-23 11:57:27 -05:00
Andreas Ackermann
29f127e226 Remove E-prefix from EBaseItemType enum 2019-12-23 11:57:26 -05:00
Andreas Ackermann
0d820ebb2c Replace string usages for BaseItemDto.Type with the EBaseItemType
Also fixing dependency substitution now that we use explicit versions
2019-12-23 11:55:09 -05:00
Lars Sørensen
006f4390a7 Translated using Weblate (Danish)
Currently translated at 49.4% (169 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/da/
2019-12-22 09:16:25 -05:00
ridaso
05bebbe67b Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-12-21 03:16:18 -05:00
Niels van Velzen
354ef42fcb Replace empty BitMapDrawable with transparent ColorDrawable 2019-12-20 22:55:41 +01:00
Niels van Velzen
bace7f8b91 Fix RecommendationBuilder not using a notification channel 2019-12-20 22:41:27 +01:00
Niels van Velzen
97644e2479 Remove background manager dim layers (androidx does not implement it) 2019-12-20 22:37:30 +01:00
Niels van Velzen
19f3088e62 Update deprecated methods in HorizontalGridPresenter 2019-12-20 22:31:59 +01:00
Niels van Velzen
3a1ca78182 Move to AndroidX fragments 2 2019-12-20 22:31:30 +01:00
Niels van Velzen
0a1d3dbf76 Move to AndroidX fragments
Uses a FragmentActivity for the MainActivity and updates RowsFragment to RowsSupportFragment
2019-12-20 22:23:45 +01:00
ridaso
102a93361c Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-12-20 01:16:19 -05:00
tanto faz
27ea77eac7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.1% (332 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2019-12-14 11:39:55 -05:00
4everfun
e19c50fe3d Translated using Weblate (Dutch)
Currently translated at 95.3% (326 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nl/
2019-12-14 11:39:55 -05:00
David
18dedd1c77 Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2019-12-14 11:39:55 -05:00
Bill Thornton
edc83a77fd Merge pull request #229 from AndreasGB/delete-empty-japanese-strings
Delete empty strings introduced by weblate from translations
2019-12-14 11:27:50 -05:00
Andreas Ackermann
41b91145d6 Delete empty strings from Indonesian translations 2019-12-14 12:48:23 +01:00
Andreas Ackermann
c77e84bbe6 Delete empty strings from Korean translations 2019-12-14 12:42:28 +01:00
Andreas Ackermann
beb8e821ae Delete empty strings from Mongolian translations 2019-12-14 12:35:20 +01:00
Andreas Ackermann
3011b7980e Delete empty strings from Slovak translations 2019-12-14 12:32:03 +01:00
Andreas Ackermann
8779286066 Delete empty strings from japanese translation
This is supposed to fix the issue that empty strings were displayed
in the japanese version instead of the english original if the string
was untranslated.
2019-12-14 11:51:35 +01:00
Niels van Velzen
c5e36251a4 Rename "build" file to "build.sh" because Gradle needs a "build" directory in the project root 2019-12-14 11:40:54 +01:00
Anthony Lavado
d68b43d328 Merge pull request #226 from thornbill/azure-triggers
Add release branch to CI triggers
2019-12-12 13:24:24 -05:00
Bill Thornton
7f2275a11a Add release branch to CI triggers 2019-12-12 13:15:30 -05:00
Federico Antoniazzi
51621715f9 Translated using Weblate (Italian)
Currently translated at 52.6% (180 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/it/
2019-12-12 07:16:08 -05:00
mpblabs
341efc62d2 Translated using Weblate (German)
Currently translated at 99.4% (340 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2019-12-12 07:16:06 -05:00
Bill Thornton
afa1f918ee Set fixed apiclient version 2019-12-11 00:06:38 -05:00
Bill Thornton
82f5dafb5d Fix unescaped apostrophe 2019-12-10 23:36:45 -05:00
Bill Thornton
2a3ed44fd5 Enable ACRA dialog by default and update text 2019-12-10 09:13:21 -05:00
tanto faz
922d413cd6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.1% (332 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2019-12-10 08:16:13 -05:00
Matzi24GR
0134b0d792 Translated using Weblate (Greek)
Currently translated at 30.7% (105 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/el/
2019-12-10 08:16:13 -05:00
Bill Thornton
ee0cbf4c10 Merge pull request #225 from nielsvanvelzen/html-descriptions
Support simple HTML elements in descriptions
2019-12-08 14:40:45 -05:00
Niels van Velzen
6b1a59224f Support simple HTML elements in descriptions 2019-12-08 11:06:58 +01:00
kmnb
29d926746e Added translation using Weblate (Indonesian) 2019-12-07 02:33:27 -05:00
Tommy Larsson
120c77ade6 Translated using Weblate (Swedish)
Currently translated at 2.9% (10 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sv/
2019-12-07 02:32:44 -05:00
tanto faz
7f1d98ecc8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.6% (320 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2019-12-07 02:32:44 -05:00
WWWesten
e7c08331e1 Translated using Weblate (Kazakh)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/kk/
2019-12-07 02:32:44 -05:00
mirfire
b29f0fd912 Translated using Weblate (French)
Currently translated at 99.7% (341 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-12-07 02:32:44 -05:00
Søren R K
a36b7c4cb4 Translated using Weblate (Danish)
Currently translated at 44.4% (152 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/da/
2019-12-07 02:32:44 -05:00
Adwin Ying
a815ea0319 Translated using Weblate (Japanese)
Currently translated at 74.6% (255 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ja/
2019-12-07 02:32:44 -05:00
Bill Thornton
4cfd5383d9 Add AcraLimiter 2019-12-05 12:38:59 -05:00
Bill Thornton
d3458164d6 Remove custom error handling 2019-12-05 12:21:23 -05:00
Bill Thornton
72ed94447a Add ACRA for crash reporting 2019-12-05 12:19:12 -05:00
Bill Thornton
fbd601213f Merge pull request #222 from joern-h/fix-audio-queue-reload-on-shuffle
Fix audio queue reload
2019-12-04 10:55:10 -06:00
dkanada
becb6295a6 Merge pull request #219 from joern-h/fix-musicartist-playall
Fixed Play on MusicArtists
2019-12-05 01:38:53 +09:00
WWWesten
5c40e50351 Translated using Weblate (Kazakh)
Currently translated at 81.6% (279 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/kk/
2019-12-03 16:15:58 -05:00
dkanada
99035900ff Merge pull request #221 from dcyrille18/beyontv-hevc-support
Add HEVC support for BeyondTV
2019-12-04 00:17:33 +09:00
dkanada
76512e3fc6 Merge pull request #218 from joern-h/fix-audio-playlists
Fixed playlist query
2019-12-04 00:17:23 +09:00
dkanada
c54ef94e03 Merge pull request #214 from nielsvanvelzen/encode-ass-subtitles
Force SSA/ASS subtitles to be encoded when not using VLC
2019-12-04 00:17:13 +09:00
dkanada
ac24a6a09e Merge pull request #213 from nielsvanvelzen/switchSubtitleStream-loop
Fix switching to encoded subtitles causing the video player to hang.
2019-12-04 00:17:02 +09:00
Jörn
2b80bac9f1 Fix audio queue reload
'MediaManager.shuffleAudioQueue()' returns while the dialog to replace or
add the queue is showing. This causes the reload of the current queue to
fire before the queue has changed.
I implemented a 'onQueueReplaced' event to fix this.
2019-12-03 10:28:49 +01:00
Jörn
19f280c294 Fix Shuffle-All on Playlists 2019-12-03 09:08:37 +01:00
Niels van Velzen
dc203ec04b Prevent calling the switchSubtitleStream function if the selected subtitle index didn't change when starting playback. 2019-12-02 21:38:02 +01:00
KingsFourze
c8fa914ec7 Translated using Weblate (Chinese (Traditional))
Currently translated at 30.1% (103 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hant/
2019-12-02 01:15:55 -05:00
Pan Renzhou
8cf33c16f4 Translated using Weblate (Chinese (Simplified))
Currently translated at 98.8% (338 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2019-12-02 01:15:49 -05:00
Ales Komarek
6d09df1de7 Translated using Weblate (Czech)
Currently translated at 98.8% (338 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/cs/
2019-12-02 01:15:48 -05:00
Cyrille DROMAS
a7eedf25db Add HEVC support for BeyondTV 2019-12-01 17:42:41 +01:00
Jörn
bc2d44c6bb Fixed Play on MusicArtists 2019-11-29 22:39:27 +01:00
TheGoose
3eee4c1db4 Translated using Weblate (English)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/en/
2019-11-29 16:15:42 -05:00
Jörn
9e31e52794 Fixed playlist query 2019-11-29 21:46:16 +01:00
踢低吸
49791c3ea3 Translated using Weblate (Chinese (Traditional))
Currently translated at 14.6% (50 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hant/
2019-11-28 13:48:34 -05:00
TheGoose
c421c2a78f Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/en_GB/
2019-11-28 13:48:34 -05:00
踢低吸
d90e8e3d29 Translated using Weblate (Chinese (Traditional))
Currently translated at 10.8% (37 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hant/
2019-11-28 05:48:31 -05:00
penguinfuko
cc792c93e4 Translated using Weblate (Chinese (Traditional))
Currently translated at 10.8% (37 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hant/
2019-11-28 05:48:30 -05:00
Niels van Velzen
4ef5fe8f69 Force SSA/ASS subtitles to be encoded when not using VLC
VLC can render them fine but the default player is unable to render ASS/SSA subtitles that use custom fonts, drawing or other special features. Forcing encoding on those font types fixes this issue.
2019-11-27 22:15:06 +01:00
Niels van Velzen
0dd2a93b21 Fix switching to encoded subtitles causing the video player to hang.
The .switchSubtitleStream() function calls the stop & play functions which would then call the switch function and this ended up looping. Adding an additional check if the to be switched to stream isn't the current already prevents this.
2019-11-27 22:06:47 +01:00
WWWesten
1c7052e5c2 Translated using Weblate (Russian)
Currently translated at 100.0% (342 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ru/
2019-11-27 13:15:47 -05:00
jeannin matthieu
e7fb835d89 Translated using Weblate (French)
Currently translated at 98.8% (338 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-11-27 13:15:47 -05:00
Thomas Schwery
0a4b452870 Translated using Weblate (French)
Currently translated at 98.8% (338 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-11-27 13:15:47 -05:00
Bill Thornton
7cc84de9c0 Merge pull request #212 from jellyfin/dkanada-patch-1
Use new version for apiclient
2019-11-27 09:32:08 -08:00
dkanada
7ac16901e2 use new version for apiclient 2019-11-28 02:22:58 +09:00
Leo Verto
1ce6d1f0f8 Translated using Weblate (German)
Currently translated at 95.6% (327 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2019-11-25 22:15:38 -05:00
Anthony Lavado
cb0d241ef1 Merge pull request #207 from AndreasGB/gradle-update
Upgrading Gradle Version to 5.4.1
2019-11-25 15:44:19 -05:00
Bill Thornton
e43901c75d Merge pull request #209 from nielsvanvelzen/fix-authorities
Use ${applicationId} in authorities so the ".debug" prefix is applied while developing
2019-11-24 07:02:13 -08:00
Ivan Poletsky
c08fdd3a69 Translated using Weblate (Russian)
Currently translated at 95.6% (327 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ru/
2019-11-24 08:15:39 -05:00
muldermatthijs
c00d610581 Translated using Weblate (Dutch)
Currently translated at 88.3% (302 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nl/
2019-11-24 08:15:38 -05:00
Christian Rodríguez Martínez
55a00fdf52 Translated using Weblate (Spanish)
Currently translated at 91.5% (313 of 342 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2019-11-24 08:15:34 -05:00
Niels van Velzen
1295dc604b Use ${applicationId} in authorities so the ".debug" prefix is applied while developing.
Fixes issue where debugging is not possible on a device with the stable Jellyfin release installed.
2019-11-24 12:08:41 +01:00
Andreas Ackermann
0fe28c1133 Conditional composite build with apiclient 2019-11-22 18:44:38 +01:00
zhuzhe1983
b35027525e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (329 of 329 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2019-11-22 04:15:36 -05:00
rokoh
906949476d Translated using Weblate (French)
Currently translated at 99.7% (328 of 329 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-11-22 04:15:36 -05:00
Libor Filípek
f7fee75206 Translated using Weblate (Czech)
Currently translated at 100.0% (329 of 329 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/cs/
2019-11-22 04:15:36 -05:00
Andreas Ackermann
101bc5b043 Upgrading Gradle Version to 5.4.1
Also adding myself to CONTRIBUTORS.md now, as this ate way too much time :D
2019-11-20 22:05:02 +01:00
dkanada
7e222f77b4 Merge pull request #198 from thornbill/style-issues
Fix style issues and untranslatable text
2019-11-20 00:56:48 +09:00
dkanada
e89a33df92 Merge pull request #196 from thornbill/fix-search-permissions
Fix search with denied permissions
2019-11-20 00:56:13 +09:00
Bill Thornton
1523dcf803 Update color background label 2019-11-19 09:58:14 -05:00
Bill Thornton
4077af5fca Fix formatting 2019-11-19 00:20:14 -05:00
Thomas Schwery
3e9c76d4f9 Translated using Weblate (French)
Currently translated at 97.6% (320 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-11-17 12:15:22 -05:00
Bill Thornton
643832c252 Fix style issues and untranslatable text 2019-11-16 02:43:05 -05:00
dkanada
54d5efba3e Merge pull request #203 from thornbill/fix-string-format
Fix string formatting
2019-11-16 16:40:52 +09:00
Bill Thornton
5480c0346d Fix string formatting 2019-11-16 02:18:44 -05:00
Bill Thornton
4c3db801d2 Merge pull request #202 from AndreasGB/more-playback-bitrates
Added more bitrate options
2019-11-16 01:53:53 -05:00
Bill Thornton
17a131648b Merge pull request #201 from AndreasGB/add-latest-tv-shows
Added Latest TV Shows to Home Fragment
2019-11-15 13:17:48 -05:00
swinkworld
814b232746 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (328 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2019-11-15 13:15:26 -05:00
tluciomiranda
c4a7764808 Translated using Weblate (Portuguese (Portugal))
Currently translated at 72.9% (239 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_PT/
2019-11-15 13:15:26 -05:00
Raphael Fuchter
4af56eb389 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (328 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2019-11-15 13:15:26 -05:00
César Cerón Pérez
098fb29f4d Translated using Weblate (Spanish (Mexico))
Currently translated at 99.4% (326 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es_MX/
2019-11-15 13:15:26 -05:00
Andreas
e028a49441 Translated using Weblate (German)
Currently translated at 99.4% (326 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/de/
2019-11-15 13:15:26 -05:00
Andreas Ackermann
9a367a2266 Privatized methods and implemented requested changes 2019-11-15 18:27:16 +01:00
Andreas Ackermann
0254645eb5 Added more bitrate options 2019-11-15 15:06:01 +01:00
Andreas Ackermann
bad5cc9a80 Added Latest TV Shows to Home Fragment 2019-11-15 12:08:03 +01:00
Bill Thornton
87fdf9d178 Fix search with denied permissions 2019-11-13 17:03:10 -05:00
Anthony Lavado
89122ecfad Merge pull request #195 from thornbill/fix-string-format
Fix string formatting
2019-11-13 17:01:20 -05:00
Bill Thornton
ad1cc403d2 Fix string formatting 2019-11-13 10:51:27 -05:00
Sander Lambrechts
ef46c9bd6f Translated using Weblate (Dutch)
Currently translated at 83.2% (273 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nl/
2019-11-09 18:15:16 -05:00
DJSweder
081aa8ce16 Translated using Weblate (Czech)
Currently translated at 83.2% (273 of 328 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/cs/
2019-11-09 18:15:16 -05:00
dkanada
7607a7c315 Merge pull request #191 from jellyfin/dkanada-patch-2
New release
2019-11-10 02:02:27 +09:00
Budkhuu
4d92f5fc50 Translated using Weblate (Mongolian)
Currently translated at 65.1% (209 of 321 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/mn/
2019-11-08 01:15:10 -05:00
dkanada
08f5601449 new release 2019-11-08 15:14:36 +09:00
dkanada
18f1f69000 Merge pull request #190 from thornbill/theme-manager
Cleanup theme manager
2019-11-08 11:42:44 +09:00
Bill Thornton
511afe1c22 Cleanup theme manager 2019-11-07 14:12:39 -05:00
dkanada
25f732c72a Merge pull request #188 from thornbill/add-server-crash
Fix version check when no servers in response
2019-11-08 03:23:21 +09:00
dkanada
fc944cc77d Merge pull request #187 from thornbill/images
Fix incorrect image types and aspect ratios
2019-11-08 03:21:12 +09:00
dkanada
47fbd86278 Merge pull request #189 from thornbill/fix-delayed-message
Fix using DelayedMessage on not the main ui thread
2019-11-07 19:20:37 +09:00
Bill Thornton
b656935ce6 Fix using DelayedMessage on not the main ui thread 2019-11-07 01:17:29 -05:00
Budkhuu
49684b5eac Added translation using Weblate (Mongolian) 2019-11-06 23:31:39 -05:00
Bill Thornton
3b6689f2a3 Fix version check when no servers in response 2019-11-06 23:27:30 -05:00
Bill Thornton
613e9c4e57 Force aspect ratio for LiveTvProgram movies 2019-11-06 21:27:06 -05:00
Bill Thornton
cda5970488 Fix program image type 2019-11-06 19:35:15 -05:00
Bill Thornton
79669bfee1 Fix non-translated strings and formatting 2019-11-06 19:27:22 -05:00
Bill Thornton
6cddce7ada Force aspect ratio for CollectionFolder types 2019-11-06 19:12:19 -05:00
Bill Thornton
dab9bb1d8d Fix formatting issues 2019-11-06 19:12:19 -05:00
jeannin matthieu
a25e380476 Translated using Weblate (French)
Currently translated at 99.1% (318 of 321 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/fr/
2019-11-05 14:15:02 -05:00
Anthony Lavado
2b7b594428 Merge pull request #185 from jellyfin/dkanada-patch-1
Bump version
2019-11-04 01:42:10 -05:00
dkanada
6e9a1f5e29 bump version 2019-11-04 15:14:40 +09:00
dkanada
5d3a391e3b Merge pull request #184 from thornbill/transcode-profile
Fix video codec in transcode profile
2019-11-04 15:13:15 +09:00
dkanada
3395bcfc0a Merge pull request #182 from thornbill/login-crash
Disable DelayedMessage to prevent crash on login
2019-11-04 15:12:53 +09:00
Bill Thornton
e22621e3bb Fix video codec in transcode profile 2019-11-04 00:37:04 -05:00
Joshua M. Boniface
878a1d4f47 Merge pull request #183 from joshuaboniface/fix-errexit
Set errexit in gradlew
2019-11-03 11:33:21 -05:00
Joshua Boniface
994a4deb26 Set errexit in gradlew
Will cause failing builds to abort properly.
2019-11-03 11:18:50 -05:00
Bill Thornton
b1f93f66ca Disable DelayedMessage to prevent crash on login 2019-11-03 11:11:25 -05:00
WWWesten
f1387a951a Translated using Weblate (Russian)
Currently translated at 100.0% (321 of 321 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ru/
2019-11-03 09:14:57 -05:00
Koos van Riemsdijk
07c67974be Translated using Weblate (Dutch)
Currently translated at 76.3% (245 of 321 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nl/
2019-11-03 09:14:57 -05:00
dkanada
17ca9b007d Merge pull request #174 from jellyfin/dkanada-patch-1
Release new version
2019-10-30 02:03:14 +09:00
dkanada
c7f516725a Release new version 2019-10-29 07:09:37 +09:00
Pan Renzhou
11a7e2f367 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (318 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2019-10-28 05:14:48 -04:00
dkanada
67e055497f Merge pull request #172 from thornbill/default-port
Remove default port
2019-10-26 15:20:32 +09:00
dkanada
528d106bad Merge pull request #171 from thornbill/playback-exception
Fix issues in playback controller and device profile
2019-10-26 15:19:52 +09:00
Bill Thornton
45bd7301df Add port number to server address hint 2019-10-25 16:12:08 -04:00
Bill Thornton
4abb9f9c3e Remove default port 2019-10-25 16:02:14 -04:00
dkanada
4ce502e1ad Merge pull request #168 from thornbill/version-check
Server sign in updates
2019-10-26 03:47:44 +09:00
Bill Thornton
d08aabd1e9 Update error message 2019-10-25 14:05:47 -04:00
Bill Thornton
7a9e3f18d5 Fix code review feedback 2019-10-25 13:50:04 -04:00
Bill Thornton
161e511f31 Fix startsWith order in Api16Builder 2019-10-25 12:30:27 -04:00
Bill Thornton
56a9ece57a Fix bitwise or in PlaybackController 2019-10-25 12:29:25 -04:00
Bill Thornton
e5321ee030 Fix NPEs in PlaybackController 2019-10-25 10:32:49 -04:00
dkanada
10dcb80f6e Merge pull request #170 from thornbill/azure-pipeline
Add Azure Pipelines config
2019-10-25 23:05:56 +09:00
nextlooper42
c72d2c417c Translated using Weblate (Slovak)
Currently translated at 3.1% (10 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/sk/
2019-10-24 20:14:40 -04:00
nextlooper42
e130fa1954 Added translation using Weblate (Slovak) 2019-10-23 20:08:19 -04:00
Pan Renzhou
02e0c98530 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (318 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2019-10-21 05:14:45 -04:00
Scott Anecito
681a96b873 Translated using Weblate (Japanese)
Currently translated at 2.8% (9 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ja/
2019-10-19 17:14:28 -04:00
Scott Anecito
98a6509483 Added translation using Weblate (Japanese) 2019-10-18 16:50:28 -04:00
tpishere
bbe79331e9 Translated using Weblate (Chinese (Simplified))
Currently translated at 44.7% (142 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/zh_Hans/
2019-10-18 07:14:33 -04:00
schbal
9700ff7f49 Translated using Weblate (Korean)
Currently translated at 67.0% (213 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ko/
2019-10-18 07:14:33 -04:00
Davi Carlos de Lira Ferreira
718f7d906e Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.5% (310 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/pt_BR/
2019-10-18 07:14:33 -04:00
Bill Thornton
b65ae67040 Add azure pipelines config 2019-10-17 14:54:12 -04:00
dkanada
c5994c620e Merge pull request #169 from thornbill/exceptions
Fix various exceptions
2019-10-18 01:28:18 +09:00
Bill Thornton
0d3d32f574 Remove unused imports 2019-10-17 12:18:22 -04:00
Bill Thornton
754a2316b7 Fix NullPointerException on SeriesTimer screen 2019-10-17 12:05:41 -04:00
Bill Thornton
939e277c2a Fix getting resource urls in debug builds 2019-10-17 12:04:18 -04:00
Bill Thornton
055e57f476 Fix infinite loop when Exception occurs in Activity initialization 2019-10-17 10:46:22 -04:00
Bill Thornton
4da0131313 Add minimum server version check 2019-10-16 10:02:20 -04:00
Bill Thornton
fa33b58155 Fix ItemRowAdapter formatting 2019-10-16 10:01:24 -04:00
Bill Thornton
4f2305fcd9 Add test for versionGreaterThanOrEqual 2019-10-15 23:45:02 -04:00
Bill Thornton
de67f8d936 Remove unused methods 2019-10-15 14:04:57 -04:00
Bill Thornton
3d0214e7c2 Refactor server sign in code 2019-10-15 10:40:37 -04:00
Bill Thornton
cb425c8da4 Update GsonJsonSerializer and LogonCredentials locations 2019-10-14 10:23:11 -04:00
Axel Gabriel Calle Granda
89129f3ae8 Translated using Weblate (Spanish)
Currently translated at 96.2% (306 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2019-10-13 18:14:23 -04:00
schbal
f82a21e9a2 Translated using Weblate (Korean)
Currently translated at 63.2% (201 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ko/
2019-10-13 18:14:14 -04:00
Marius Lindvall
206ca83bf0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.7% (317 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/nb_NO/
2019-10-11 17:14:25 -04:00
WWWesten
41b3690737 Translated using Weblate (Russian)
Currently translated at 100.0% (318 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ru/
2019-10-11 17:14:25 -04:00
pucherot
4dfc83d374 Translated using Weblate (Spanish)
Currently translated at 61.6% (196 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2019-10-11 17:14:25 -04:00
Anthony Lavado
33ddaa8c0e Merge pull request #165 from thornbill/badges
Add badges to README
2019-10-09 23:07:11 -04:00
Bill Thornton
71aba0a6e1 Add badges to README 2019-10-09 11:34:23 -04:00
Joshua M. Boniface
5b47f0da6c Merge pull request #164 from thornbill/duplicate-locale
Remove duplicate French locale
2019-10-09 10:17:33 -04:00
Bill Thornton
dcf5fd577b Remove duplicate French locale 2019-10-09 10:11:29 -04:00
schbal
de3dc5f901 Translated using Weblate (Korean)
Currently translated at 62.6% (199 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ko/
2019-10-09 09:53:48 -04:00
schbal
8746a9abd1 Translated using Weblate (Korean)
Currently translated at 35.8% (114 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ko/
2019-10-08 18:14:13 -04:00
WWWesten
bdb56539d4 Translated using Weblate (Russian)
Currently translated at 93.4% (297 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/ru/
2019-10-08 18:14:10 -04:00
WWWesten
6fa4cd9689 Translated using Weblate (Kazakh)
Currently translated at 81.1% (258 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/kk/
2019-10-08 18:14:08 -04:00
angeltonio
4e2ffb150b Translated using Weblate (Spanish)
Currently translated at 54.4% (173 of 318 strings)

Translation: Jellyfin/Jellyfin AndroidTV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/es/
2019-10-08 18:14:08 -04:00
schbal
37c896a869 Added translation using Weblate (Korean) 2019-10-07 08:12:29 -04:00
dkanada
f478ea5a77 Merge pull request #162 from thornbill/fix-arrays-aslist
Fix exception due to adding elements to fixed length arrays
2019-10-01 12:56:51 +09:00
Bill Thornton
1c35ce4d47 Fix exception due to adding elements to fixed length arrays 2019-09-30 23:01:30 -04:00
dkanada
eb7c42c8dd Merge pull request #161 from thornbill/wan-removal
Update apiclient getAddress method name
2019-09-29 15:50:32 +09:00
dkanada
9480088d74 Merge pull request #160 from thornbill/remove-report-error
Remove report error dialog
2019-09-29 15:50:02 +09:00
Bill Thornton
0db9c6797b Update apiclient getAddress method name 2019-09-28 22:43:16 -04:00
Bill Thornton
9551b92273 Remove report error dialog 2019-09-28 22:30:25 -04:00
dkanada
9bd3869001 Merge pull request #156 from thornbill/next-up-duration
Update next up duration
2019-09-27 11:30:12 +09:00
Bill Thornton
5819634f1f Fix formatting 2019-09-26 14:32:52 -04:00
Bill Thornton
59e2e347ed Update next up duration 2019-09-21 22:01:28 -04:00
Joshua M. Boniface
5aa80924c3 Merge pull request #148 from dkanada/icon
Add some nice icons and move the play button to the button row
2019-09-10 10:41:53 -04:00
dkanada
e02f07929b fix build error 2019-09-06 20:06:54 -07:00
dkanada
4fd86d8f4b fix extra badge position on card view 2019-09-03 04:08:34 -07:00
dkanada
e2551f9400 move play button to button row in video activity 2019-09-03 03:58:27 -07:00
dkanada
e44e7b6231 add new icons to match web style 2019-09-03 02:12:11 -07:00
Anthony Lavado
bb77c4f740 Merge pull request #141 from dkanada/layout
Layout changes to improve music libraries
2019-08-31 00:11:36 -04:00
dkanada
c6402b6939 improve ItemListView layout for albums 2019-08-29 00:34:40 -07:00
dkanada
134bdbc166 rework the settings activity 2019-08-29 00:34:29 -07:00
dkanada
128a68f52f center extra badge vertically on card details 2019-08-25 04:39:46 -07:00
dkanada
6fca91e7f3 more code cleanup 2019-08-25 04:22:10 -07:00
dkanada
f2db849f00 rename all icons into categories 2019-08-25 02:17:04 -07:00
dkanada
7b8b1ca150 minor layout changes to now playing audio activity 2019-08-25 02:12:39 -07:00
dkanada
41a6e83b32 code cleanup 2019-08-25 02:09:49 -07:00
dkanada
7e88ffe4af replace equalizer animation with simple icon 2019-08-25 02:09:49 -07:00
Joshua M. Boniface
52cab96832 Merge pull request #139 from dkanada/strings
String improvements
2019-08-20 23:29:19 -04:00
dkanada
671f38c51c use normal text color for now playing activity 2019-08-20 03:50:00 -07:00
dkanada
ff83a253be improve status view for music playback 2019-08-20 03:00:59 -07:00
dkanada
0341b9a79d more string improvements 2019-08-20 01:47:48 -07:00
dkanada
dc808dec66 standardize preference strings 2019-08-20 00:55:47 -07:00
dkanada
2397e6371a allow translations for all preferences 2019-08-20 00:36:52 -07:00
dkanada
a23c27b57b modify strings for all toggle buttons 2019-08-20 00:33:50 -07:00
dkanada
9a55916521 small changes to the bottom row of the home fragment 2019-08-20 00:14:56 -07:00
Anthony Lavado
384d891b65 Merge pull request #137 from thornbill/fix-removed-methods
Fix removed methods
2019-08-17 02:31:33 -04:00
Bill Thornton
11f75f24fa Remove usage of getUserLinkType 2019-08-14 23:57:27 -04:00
Bill Thornton
5565cbc475 Fix usage of removed methods in apiclient 2019-08-14 18:50:36 -04:00
Joshua M. Boniface
c84be824fd Merge pull request #134 from anthonylavado/version-bump
Update version
2019-08-04 22:25:14 -04:00
Anthony Lavado
46108e81c4 Update version
Updates the version to create a new release
2019-08-04 17:44:55 -04:00
Anthony Lavado
b5f79bc0b9 Merge pull request #124 from thornbill/subtitle-color
Fix color of subtitles
2019-07-25 17:16:11 -04:00
Anthony Lavado
30810cc293 Merge pull request #123 from thornbill/fix-recommend-crash
Fix crash due to null context in RecommendationBuilder
2019-07-25 16:49:00 -04:00
Joshua M. Boniface
df7ce49a25 Merge pull request #131 from thornbill/vlc-release
Fix error when libvlc is not properly released
2019-07-25 16:46:52 -04:00
Bill Thornton
67e13d7505 Fix error when libvlc is not properly released 2019-07-25 15:49:05 -04:00
Bill Thornton
8a82fb392d Fix color of subtitles 2019-07-13 02:56:50 -04:00
Bill Thornton
15d9c26352 Fix crash due to null context in RecommendationBuilder 2019-07-12 23:57:21 -04:00
Anthony Lavado
9c8d4f24e1 Merge pull request #120 from thornbill/allow-http
Allow http traffic
2019-07-11 01:12:48 -04:00
Anthony Lavado
cd2890a4cf Merge pull request #121 from thornbill/libvlc
Use official libvlc build
2019-07-11 01:12:31 -04:00
Bill Thornton
b1b7c813b2 Allow http traffic 2019-07-11 00:10:05 -04:00
Bill Thornton
61222eb0fa Use official libvlc build 2019-07-11 00:08:59 -04:00
Anthony Lavado
40c577fa31 Merge pull request #118 from thornbill/androidx
Migrate to androidx libraries
2019-07-09 22:40:36 -04:00
Bill Thornton
e024acb1a9 Migrate to androidx libraries 2019-07-09 09:26:32 -04:00
336 changed files with 8735 additions and 5657 deletions

16
.ci/azure-pipelines.yml Normal file
View File

@@ -0,0 +1,16 @@
trigger:
- master
- release
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Gradle@2
inputs:
gradleWrapperFile: 'gradlew'
tasks: 'build'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
[*]
end_of_line = lf
insert_final_newline = true
[{*.kts, *.kt}]
charset = utf-8
indent_style = tab
tab_width = 4

View File

@@ -6,6 +6,9 @@
- [grafixeyehero](https://github.com/grafixeyehero)
- [BnMcG](https://github.com/bnmcg)
- [Letroll](https://github.com/letroll)
- [AndreasGB](https://github.com/andreasgb)
- [joern-h](https://github.com/joern-h)
- [Niels van Velzen](https://github.com/nielsvanvelzen)
# Emby Contributors

View File

@@ -1,6 +1,50 @@
<h1 align="center">Jellyfin Android TV</h1>
<h3 align="center">Part of the <a href="https://jellyfin.media">Jellyfin Project</a></h3>
---
<p align="center">
Jellyfin Android TV is a Jellyfin client for Android TV, nVidia Shield, and Amazon Fire TV devices.
<img alt="Logo banner" src="https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/branding/SVG/banner-logo-solid.svg?sanitize=true"/>
<br/><br/>
<a href="https://github.com/jellyfin/jellyfin-androidtv">
<img alt="GPL 2.0 License" src="https://img.shields.io/github/license/jellyfin/jellyfin-androidtv.svg"/>
</a>
<a href="https://github.com/jellyfin/jellyfin-androidtv/releases">
<img alt="Current Release" src="https://img.shields.io/github/release/jellyfin/jellyfin-androidtv.svg"/>
</a>
<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv/?utm_source=widget">
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-androidtv/svg-badge.svg" alt="Translation status" />
</a>
<br/>
<a href="https://opencollective.com/jellyfin">
<img alt="Donate" src="https://img.shields.io/opencollective/all/jellyfin.svg?label=backers"/>
</a>
<a href="https://features.jellyfin.org">
<img alt="Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/>
</a>
<a href="https://forum.jellyfin.org">
<img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/>
</a>
<a href="https://matrix.to/#/+jellyfin:matrix.org">
<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/>
</a>
<a href="https://www.reddit.com/r/jellyfin">
<img alt="Join our Subreddit" src="https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg"/>
</a>
<br/>
<a href="https://play.google.com/store/apps/details?id=org.jellyfin.androidtv">
<img width="200" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" alt="Get it on Google Play"/>
</a>
<br/>
<a href="https://www.amazon.com/gp/aw/d/B07TX7Z725">
<img width="170" src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-black.png" alt="Available at Amazon Appstore"/>
</a>
<br/>
<a href="https://repo.jellyfin.org/releases/client/androidtv/">Download archive</a>
</p>
Jellyfin Android TV is a Jellyfin client for Android TV, nVidia Shield, and Amazon Fire TV devices. We welcome all contributions and pull requests! If you have a larger feature in mind please open an issue so we can discuss the implementation before you start. Translations can be improved very easily from our <a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-androidtv">Weblate</a> instance. Look through the following graphic to see if your native language could use some work!
<a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget">
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-androidtv/multi-auto.svg" alt="Detailed Translation Status"/>
</a>

View File

@@ -1,14 +1,21 @@
apply plugin: 'com.android.application'
def version = "0.9.0"
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
def version = "0.11.1"
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 900
versionCode 906
versionName version
}
@@ -22,33 +29,43 @@ android {
debuggable true
}
}
flavorDimensions "version"
productFlavors {
google {
dimension = "version"
versionName = version + "g"
}
amazon {
dimension = "version"
versionName = version + "a"
}
}
}
dependencies {
def acraVersion = '5.4.0'
implementation fileTree(include: ['*.jar'], dir: 'libs')
amazonImplementation 'com.github.jellyfin:exomedia:amazon~2.5.6-SNAPSHOT'
googleImplementation 'com.devbrackets.android:exomedia:2.5.6'
implementation 'com.github.jellyfin:jellyfin-apiclient-java:master-SNAPSHOT'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:leanback-v17:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:palette-v7:28.0.0'
// This is split like this because this is a modular gradle project on jitpack
// see https://github.com/jitpack/gradle-modular
// The split allows dependency substitution to work in the settings.gradle file,
// at least for the 'library' part. 'android' part is still broken somehow, but this
// is better than nothing.
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.5.0'
implementation 'com.github.jellyfin.jellyfin-apiclient-java:library:0.5.0'
implementation 'com.amazon.android:exoplayer:2.10.6'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.leanback:leanback:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.palette:palette:1.0.0'
implementation 'com.squareup.picasso:picasso:2.3.2'
implementation 'com.github.bumptech.glide:glide:3.7.0'
implementation 'com.squareup.okhttp:okhttp:2.7.0'
implementation 'com.squareup.okhttp:okhttp-urlconnection:2.7.0'
implementation 'com.google.guava:guava:18.0'
implementation 'com.flaviofaria:kenburnsview:1.0.6'
implementation 'org.videolan.android:libvlc-all:3.1.12'
implementation "ch.acra:acra-http:$acraVersion"
implementation "ch.acra:acra-dialog:$acraVersion"
implementation "ch.acra:acra-limiter:$acraVersion"
testImplementation 'junit:junit:4.12'
implementation "androidx.core:core-ktx:1.1.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.tvprovider:tvprovider:1.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
}
repositories {
mavenCentral()
}

View File

@@ -1 +0,0 @@
f5887942aa8e8f94380f218e5e0cd9ddf3974485

View File

@@ -11,6 +11,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Needed for ChannelManager on older devices (< API 23) -->
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
@@ -20,22 +23,23 @@
<application
android:name=".TvApp"
android:banner="@drawable/app_icon"
android:icon="@drawable/logoicon512"
android:banner="@drawable/app_logo"
android:icon="@drawable/app_icon_large"
android:label="@string/app_name"
android:largeHeap="true"
android:theme="@style/Theme.Jellyfin"
android:allowBackup="false"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".browsing.MainActivity"
android:label="@string/app_name"
android:logo="@drawable/app_icon"
android:logo="@drawable/app_logo"
android:screenOrientation="landscape" />
<activity
android:name=".startup.StartupActivity"
android:label="@string/app_name"
android:logo="@drawable/app_icon"
android:logo="@drawable/app_logo"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -80,13 +84,6 @@
</intent-filter>
</receiver>
<provider
android:authorities="org.jellyfin.androidtv.recommendations"
android:name=".integration.RecommendationContentProvider"
android:exported="true"
android:grantUriPermissions="true"
tools:ignore="ExportedContentProvider" />
<activity
android:name=".details.ExpandedTextActivity"
android:label="@string/title_activity_expanded_text" />

View File

@@ -1,59 +1,71 @@
package org.jellyfin.androidtv;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.media.AudioManager;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.graphics.Palette;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
import org.acra.annotation.AcraHttpSender;
import org.acra.annotation.AcraLimiter;
import org.acra.sender.HttpSender;
import org.jellyfin.androidtv.base.BaseActivity;
import org.jellyfin.androidtv.livetv.TvManager;
import org.jellyfin.androidtv.model.DisplayPriorityType;
import org.jellyfin.androidtv.model.LogonCredentials;
import org.jellyfin.androidtv.playback.ExternalPlayerActivity;
import org.jellyfin.androidtv.playback.MediaManager;
import org.jellyfin.androidtv.playback.PlaybackController;
import org.jellyfin.androidtv.playback.PlaybackManager;
import org.jellyfin.androidtv.playback.PlaybackOverlayActivity;
import org.jellyfin.androidtv.search.SearchActivity;
import org.jellyfin.androidtv.startup.LogonCredentials;
import org.jellyfin.androidtv.util.DeviceUtils;
import org.jellyfin.androidtv.util.Utils;
import java.util.Calendar;
import java.util.HashMap;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.IConnectionManager;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.interaction.GsonJsonSerializer;
import org.jellyfin.apiclient.interaction.VolleyHttpClient;
import org.jellyfin.apiclient.logging.AndroidLogger;
import org.jellyfin.apiclient.model.configuration.ServerConfiguration;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.entities.DisplayPreferences;
import org.jellyfin.apiclient.model.logging.ILogger;
import org.jellyfin.apiclient.model.net.EndPointInfo;
import org.jellyfin.apiclient.model.system.SystemInfo;
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
public class TvApp extends Application implements ActivityCompat.OnRequestPermissionsResultCallback {
import java.util.Calendar;
import java.util.HashMap;
import androidx.core.content.ContextCompat;
import androidx.palette.graphics.Palette;
@AcraCore(buildConfigClass = BuildConfig.class)
@AcraHttpSender(
uri = "https://collector.tracepot.com/a2eda9d9",
httpMethod = HttpSender.Method.POST
)
@AcraDialog(
resTitle = R.string.acra_dialog_title,
resText = R.string.acra_dialog_text,
resTheme = R.style.Theme_Jellyfin
)
@AcraLimiter
public class TvApp extends Application {
// The minimum supported server version. Trying to connect to an older server will display an error.
public static final String MINIMUM_SERVER_VERSION = "10.3.0";
public static final String CREDENTIALS_PATH = "org.jellyfin.androidtv.login.json";
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;
@@ -61,7 +73,6 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
public static final int LIVE_TV_SERIES_OPTION_ID = 5000;
private static final String TAG = "Jellyfin-AndroidTV";
private static final int SEARCH_PERMISSION = 0;
private ILogger logger;
private IConnectionManager connectionManager;
@@ -69,7 +80,6 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
private GsonJsonSerializer serializer;
private static TvApp app;
private UserDto currentUser;
private SystemInfo currentSystemInfo;
private BaseItemDto currentPlayingItem;
private BaseItemDto lastPlayedItem;
private PlaybackController playbackController;
@@ -98,8 +108,6 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
private long lastMusicPlayback = System.currentTimeMillis();
private long lastUserInteraction = System.currentTimeMillis();
private boolean searchAllowed = Build.VERSION.SDK_INT < 23;
private GradientDrawable currentBackgroundGradient;
private boolean audioMuted;
@@ -110,6 +118,13 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
private LogonCredentials configuredAutoCredentials;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
ACRA.init(this);
}
@Override
public void onCreate() {
super.onCreate();
@@ -120,17 +135,6 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
setCurrentBackgroundGradient(new int[] {ContextCompat.getColor(this, R.color.lb_default_brand_color_dark), ContextCompat.getColor(this, R.color.lb_default_brand_color)});
logger.Info("Application object created");
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
logger.FatalException("Uncaught Exception", new Exception(ex));
ex.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
});
}
public static TvApp getApplication() {
@@ -222,120 +226,13 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
this.playbackController = playbackController;
}
public SystemInfo getCurrentSystemInfo() { return currentSystemInfo; }
public void loadSystemInfo() {
if (getApiClient() != null) {
getApiClient().GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo response) {
currentSystemInfo = response;
logger.Info("Current server is " + response.getServerName() + " (ver " + response.getVersion() + ") running on " + response.getOperatingSystemDisplayName());
//Server compat warning
if (getCurrentActivity() != null && !Utils.versionGreaterThanOrEqual(currentSystemInfo.getVersion(), "3.0.5882.0")) {
new AlertDialog.Builder(getCurrentActivity())
.setTitle("Incompatible Server Version")
.setMessage("Please update your Jellyfin Server to avoid potential seeking problems during playback.")
.setPositiveButton(getString(R.string.btn_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setCancelable(false)
.show();
}
}
@Override
public void onError(Exception exception) {
logger.ErrorException("Unable to obtain system info.", exception);
}
});
//Also get server configuration and fill in max remote bitrate if we are remote
getApiClient().GetEndPointInfo(new Response<EndPointInfo>() {
@Override
public void onResponse(EndPointInfo response) {
if (!response.getIsInNetwork()) {
getApiClient().GetServerConfigurationAsync(new Response<ServerConfiguration>() {
@Override
public void onResponse(ServerConfiguration response) {
serverConfiguration = response;
maxRemoteBitrate = serverConfiguration.getRemoteClientBitrateLimit();
getLogger().Info("Server bitrate limit set to ", maxRemoteBitrate);
}
@Override
public void onError(Exception exception) {
getLogger().ErrorException("Unable to retrieve server configuration",exception);
}
});
} else {
getLogger().Info("** Local connection - no server bitrate limit");
}
}
});
}
}
public void showSearch(final Activity activity, boolean musicOnly) {
if (!searchAllowed && ContextCompat.checkSelfPermission(activity,
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
//request necessary permission
logger.Info("Requesting search permission...");
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO)) {
//show explanation
logger.Info("Show rationale for permission");
new AlertDialog.Builder(activity)
.setTitle("Search Permission")
.setMessage("Search requires permission to record audio in order to use the microphone for voice search")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
ActivityCompat.requestPermissions(activity, new String[] {Manifest.permission.RECORD_AUDIO}, SEARCH_PERMISSION);
}
}).show();
} else {
ActivityCompat.requestPermissions(activity, new String[] {Manifest.permission.RECORD_AUDIO}, SEARCH_PERMISSION);
}
} else {
showSearchInternal(activity, musicOnly);
}
}
private void showSearchInternal(Context activity, boolean musicOnly) {
Intent intent = new Intent(activity, SearchActivity.class);
if (musicOnly) intent.putExtra("MusicOnly", true);
intent.putExtra("MusicOnly", musicOnly);
activity.startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case SEARCH_PERMISSION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay!
searchAllowed = true;
showSearchInternal(getCurrentActivity(), false);
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Utils.showToast(this, "Search not allowed");
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
public void showMessage(String title, String msg) {
if (currentActivity != null) {
currentActivity.showMessage(title, msg);
@@ -372,23 +269,23 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
return getPrefs().getString("pref_login_behavior", "0").equals("1") && getConfiguredAutoCredentials().getServerInfo().getId() != null;
}
public boolean useExternalPlayer(String itemType) {
public boolean useExternalPlayer(BaseItemType itemType) {
switch (itemType) {
case "Movie":
case "Episode":
case "Video":
case "Series":
case "Recording":
return getPrefs().getBoolean("pref_video_use_external", false);
case "TvChannel":
case "Program":
case Movie:
case Episode:
case Video:
case Series:
case Recording:
return getPrefs().getString("pref_video_player", "auto").equals("external");
case TvChannel:
case Program:
return getPrefs().getBoolean("pref_live_tv_use_external", false);
default:
return false;
}
}
public Class getPlaybackActivityClass(String itemType) {
public Class getPlaybackActivityClass(BaseItemType itemType) {
return useExternalPlayer(itemType) ? ExternalPlayerActivity.class : PlaybackOverlayActivity.class;
}
@@ -623,14 +520,6 @@ public class TvApp extends Application implements ActivityCompat.OnRequestPermis
this.httpClient = httpClient;
}
public boolean isSearchAllowed() {
return searchAllowed;
}
public void setSearchAllowed(boolean searchAllowed) {
this.searchAllowed = searchAllowed;
}
public BaseItemDto getLastPlayedItem() {
return lastPlayedItem;
}

View File

@@ -1,6 +1,5 @@
package org.jellyfin.androidtv.base;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -17,8 +16,10 @@ import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.util.Utils;
public class BaseActivity extends Activity {
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentActivity;
public abstract class BaseActivity extends FragmentActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
private TvApp app = TvApp.getApplication();
private long timeoutInterval = 3600000;
private Handler handler = new Handler();
@@ -38,7 +39,7 @@ public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
timeoutInterval = Long.parseLong(PreferenceManager.getDefaultSharedPreferences(app).getString("pref_auto_logoff_timeout","3600000"));
timeoutInterval = Long.parseLong(PreferenceManager.getDefaultSharedPreferences(app).getString("pref_auto_logoff_timeout", "3600000"));
startAutoLogoffLoop();
TvApp.getApplication().setCurrentActivity(this);
@@ -79,8 +80,12 @@ public class BaseActivity extends Activity {
if (messageUi != null && !isFinishing()) {
messageTitle.setText(title);
messageMessage.setText(msg);
if (icon != null) messageIcon.setImageResource(icon);
if (background != null) messageUi.setBackgroundResource(background);
if (icon != null) {
messageIcon.setImageResource(icon);
}
if (background != null) {
messageUi.setBackgroundResource(background);
}
messageUi.animate().x(msgPos).alpha(1).setDuration(300);
handler.postDelayed(new Runnable() {
@Override
@@ -89,7 +94,7 @@ public class BaseActivity extends Activity {
messageUi.animate().alpha(0).x(farRight).setDuration(300);
}
}
},timeout);
}, timeout);
}
}
@@ -122,7 +127,9 @@ public class BaseActivity extends Activity {
@Override
protected void onDestroy() {
if (handler != null && loop != null) handler.removeCallbacks(loop);
if (handler != null && loop != null) {
handler.removeCallbacks(loop);
}
super.onDestroy();
}
@@ -133,7 +140,9 @@ public class BaseActivity extends Activity {
}
private void startAutoLogoffLoop() {
if (loop != null) return;
if (loop != null) {
return;
}
loop = new Runnable() {
@Override
@@ -165,7 +174,9 @@ public class BaseActivity extends Activity {
}
public void sendMessage(CustomMessage message) {
if (messageListener != null) messageListener.onMessageReceived(message);
if (messageListener != null) {
messageListener.onMessageReceived(message);
}
}
@Override

View File

@@ -2,13 +2,13 @@ package org.jellyfin.androidtv.browsing;
import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.widget.Toast;
import org.jellyfin.androidtv.R;
@@ -66,13 +66,12 @@ public class BrowseFolderFragment extends StdBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(BY_LETTER, mApplication.getString(R.string.lbl_by_letter), R.drawable.byletter));
gridRowAdapter.add(new GridButton(BY_LETTER, mApplication.getString(R.string.lbl_by_letter), R.drawable.tile_letters));
if (itemTypeString != null && itemTypeString.equals("Movie"))
gridRowAdapter.add(new GridButton(SUGGESTED, mApplication.getString(R.string.lbl_suggested), R.drawable.suggestions));
gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.genres));
gridRowAdapter.add(new GridButton(PERSONS, mApplication.getString(R.string.lbl_performers), R.drawable.actors));
gridRowAdapter.add(new GridButton(SUGGESTED, mApplication.getString(R.string.lbl_suggested), R.drawable.tile_suggestions));
gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.tile_genres));
gridRowAdapter.add(new GridButton(PERSONS, mApplication.getString(R.string.lbl_performers), R.drawable.tile_actors));
rowAdapter.add(new ListRow(gridHeader, gridRowAdapter));
}
}
@@ -86,7 +85,6 @@ public class BrowseFolderFragment extends StdBrowseFragment {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof GridButton) {
switch (((GridButton) item).getId()) {
case BY_LETTER:
@@ -96,7 +94,6 @@ public class BrowseFolderFragment extends StdBrowseFragment {
getActivity().startActivity(intent);
break;
case GENRES:
Intent genreIntent = new Intent(getActivity(), ByGenreActivity.class);
genreIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder));
@@ -104,7 +101,6 @@ public class BrowseFolderFragment extends StdBrowseFragment {
getActivity().startActivity(genreIntent);
break;
case SUGGESTED:
Intent suggIntent = new Intent(getActivity(), SuggestedMoviesActivity.class);
suggIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder));
@@ -112,7 +108,6 @@ public class BrowseFolderFragment extends StdBrowseFragment {
getActivity().startActivity(suggIntent);
break;
case PERSONS:
Intent personIntent = new Intent(getActivity(), BrowsePersonsActivity.class);
personIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(mFolder));
@@ -120,10 +115,8 @@ public class BrowseFolderFragment extends StdBrowseFragment {
getActivity().startActivity(personIntent);
break;
default:
Toast.makeText(getActivity(), item.toString() + mApplication.getString(R.string.msg_not_implemented), Toast.LENGTH_SHORT)
.show();
Toast.makeText(getActivity(), item.toString() + mApplication.getString(R.string.msg_not_implemented), Toast.LENGTH_SHORT).show();
break;
}
}

View File

@@ -6,6 +6,7 @@ import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.model.ChangeTriggerType;
import org.jellyfin.androidtv.querying.StdItemQuery;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.querying.ArtistsQuery;
import org.jellyfin.apiclient.model.querying.ItemFields;
@@ -30,7 +31,7 @@ public class BrowseGridFragment extends StdGridFragment {
protected void setupQueries(IGridLoader gridLoader) {
StdItemQuery query = new StdItemQuery(new ItemFields[] {ItemFields.PrimaryImageAspectRatio});
query.setParentId(mParentId);
if (mFolder.getType().equals("UserView") || mFolder.getType().equals("CollectionFolder")) {
if (mFolder.getBaseItemType() == BaseItemType.UserView || mFolder.getBaseItemType() == BaseItemType.CollectionFolder) {
String type = mFolder.getCollectionType() != null ? mFolder.getCollectionType().toLowerCase() : "";
switch (type) {
case "movies":

View File

@@ -1,9 +1,9 @@
package org.jellyfin.androidtv.browsing;
import android.os.Handler;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
@@ -19,6 +19,7 @@ import java.util.List;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.entities.LocationType;
import org.jellyfin.apiclient.model.livetv.RecordingGroupQuery;
import org.jellyfin.apiclient.model.livetv.RecordingQuery;
@@ -119,7 +120,7 @@ public class BrowseRecordingsFragment extends EnhancedBrowseFragment {
programInfo.setChannelName(timer.getChannelName());
programInfo.setName(Utils.getSafeValue(timer.getName(), "Unknown"));
TvApp.getApplication().getLogger().Warn("No program info for timer %s. Creating one...", programInfo.getName());
programInfo.setType("Program");
programInfo.setBaseItemType(BaseItemType.Program);
programInfo.setTimerId(timer.getId());
programInfo.setSeriesTimerId(timer.getSeriesTimerId());
programInfo.setStartDate(timer.getStartDate());
@@ -158,8 +159,8 @@ public class BrowseRecordingsFragment extends EnhancedBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(SCHEDULE, TvApp.getApplication().getString(R.string.lbl_schedule), R.drawable.clock));
gridRowAdapter.add(new GridButton(SERIES, mActivity.getString(R.string.lbl_series_recordings), R.drawable.seriestimerp));
gridRowAdapter.add(new GridButton(SCHEDULE, TvApp.getApplication().getString(R.string.lbl_schedule), R.drawable.tile_port_time));
gridRowAdapter.add(new GridButton(SERIES, mActivity.getString(R.string.lbl_series_recordings), R.drawable.tile_port_series_timer));
rowAdapter.add(new ListRow(gridHeader, gridRowAdapter));
}

View File

@@ -1,9 +1,8 @@
package org.jellyfin.androidtv.browsing;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
@@ -21,6 +20,7 @@ import java.util.List;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.entities.LocationType;
import org.jellyfin.apiclient.model.entities.SortOrder;
import org.jellyfin.apiclient.model.livetv.LiveTvChannelQuery;
@@ -39,29 +39,13 @@ import org.jellyfin.apiclient.model.querying.LatestItemsQuery;
import org.jellyfin.apiclient.model.querying.NextUpQuery;
import org.jellyfin.apiclient.model.results.TimerInfoDtoResult;
/**
* Created by Eric on 12/4/2014.
*/
public class BrowseViewFragment extends EnhancedBrowseFragment {
boolean isLiveTvLibrary;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
}
private boolean isLiveTvLibrary;
@Override
protected void setupQueries(final IRowLoader rowLoader) {
String type = mFolder.getCollectionType() != null ? mFolder.getCollectionType().toLowerCase() : "";
switch (type)
{
switch (type) {
case "movies":
itemTypeString = "Movie";
@@ -77,15 +61,15 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
resumeMovies.setFilters(new ItemFilter[]{ItemFilter.IsResumable});
resumeMovies.setSortBy(new String[]{ItemSortBy.DatePlayed});
resumeMovies.setSortOrder(SortOrder.Descending);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_continue_watching), resumeMovies, 0, new ChangeTriggerType[] {ChangeTriggerType.MoviePlayback}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_continue_watching), resumeMovies, 0, new ChangeTriggerType[]{ChangeTriggerType.MoviePlayback}));
//Latest
LatestItemsQuery latestMovies = new LatestItemsQuery();
latestMovies.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestMovies.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestMovies.setParentId(mFolder.getId());
latestMovies.setLimit(50);
latestMovies.setImageTypeLimit(1);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest), latestMovies, new ChangeTriggerType[] {ChangeTriggerType.MoviePlayback, ChangeTriggerType.LibraryUpdated}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest), latestMovies, new ChangeTriggerType[]{ChangeTriggerType.MoviePlayback, ChangeTriggerType.LibraryUpdated}));
//Favorites
StdItemQuery favorites = new StdItemQuery();
@@ -95,7 +79,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
favorites.setImageTypeLimit(1);
favorites.setFilters(new ItemFilter[]{ItemFilter.IsFavorite});
favorites.setSortBy(new String[]{ItemSortBy.SortName});
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_favorites), favorites, 60, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_favorites), favorites, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate}));
//Collections
StdItemQuery collections = new StdItemQuery();
@@ -104,7 +88,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
collections.setImageTypeLimit(1);
//collections.setParentId(mFolder.getId());
collections.setSortBy(new String[]{ItemSortBy.SortName});
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_collections), collections, 60, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_collections), collections, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}));
rowLoader.loadRows(mRows);
break;
@@ -117,8 +101,8 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
nextUpQuery.setLimit(50);
nextUpQuery.setParentId(mFolder.getId());
nextUpQuery.setImageTypeLimit(1);
nextUpQuery.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
mRows.add(new BrowseRowDef(mApplication.getResources().getString(R.string.lbl_next_up), nextUpQuery, new ChangeTriggerType[] {ChangeTriggerType.TvPlayback}));
nextUpQuery.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
mRows.add(new BrowseRowDef(mApplication.getResources().getString(R.string.lbl_next_up), nextUpQuery, new ChangeTriggerType[]{ChangeTriggerType.TvPlayback}));
//Premieres
if (mApplication.getPrefs().getBoolean("pref_enable_premieres", false)) {
@@ -140,13 +124,13 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
//Latest content added
LatestItemsQuery latestSeries = new LatestItemsQuery();
latestSeries.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestSeries.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestSeries.setIncludeItemTypes(new String[]{"Episode"});
latestSeries.setGroupItems(true);
latestSeries.setParentId(mFolder.getId());
latestSeries.setLimit(50);
latestSeries.setImageTypeLimit(1);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest), latestSeries, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest), latestSeries, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}));
//Favorites
StdItemQuery tvFavorites = new StdItemQuery();
@@ -156,7 +140,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
tvFavorites.setImageTypeLimit(1);
tvFavorites.setFilters(new ItemFilter[]{ItemFilter.IsFavorite});
tvFavorites.setSortBy(new String[]{ItemSortBy.SortName});
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_favorites), tvFavorites, 60, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_favorites), tvFavorites, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate}));
rowLoader.loadRows(mRows);
break;
@@ -164,13 +148,13 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
//Latest
LatestItemsQuery latestAlbums = new LatestItemsQuery();
latestAlbums.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestAlbums.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestAlbums.setIncludeItemTypes(new String[]{"Audio"});
latestAlbums.setGroupItems(true);
latestAlbums.setImageTypeLimit(1);
latestAlbums.setParentId(mFolder.getId());
latestAlbums.setLimit(50);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest), latestAlbums, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest), latestAlbums, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}));
//Last Played
StdItemQuery lastPlayed = new StdItemQuery();
@@ -183,7 +167,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
lastPlayed.setSortOrder(SortOrder.Descending);
lastPlayed.setEnableTotalRecordCount(false);
lastPlayed.setLimit(50);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_last_played), lastPlayed, 0, false, true, new ChangeTriggerType[] {ChangeTriggerType.MusicPlayback, ChangeTriggerType.LibraryUpdated}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_last_played), lastPlayed, 0, false, true, new ChangeTriggerType[]{ChangeTriggerType.MusicPlayback, ChangeTriggerType.LibraryUpdated}));
//Favorites
StdItemQuery favAlbums = new StdItemQuery();
@@ -193,17 +177,16 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
favAlbums.setImageTypeLimit(1);
favAlbums.setFilters(new ItemFilter[]{ItemFilter.IsFavorite});
favAlbums.setSortBy(new String[]{ItemSortBy.SortName});
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_favorites), favAlbums, 60, false, true, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate}));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_favorites), favAlbums, 60, false, true, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate}));
//AudioPlaylists
StdItemQuery playlists = new StdItemQuery(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.CumulativeRunTimeTicks});
StdItemQuery playlists = new StdItemQuery(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.CumulativeRunTimeTicks});
playlists.setIncludeItemTypes(new String[]{"Playlist"});
playlists.setMediaTypes(new String[] {"Audio"});
playlists.setImageTypeLimit(1);
playlists.setRecursive(true);
playlists.setSortBy(new String[]{ItemSortBy.DateCreated});
playlists.setSortOrder(SortOrder.Descending);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_playlists), playlists, 60, false, true, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated}, QueryType.AudioPlaylists));
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_playlists), playlists, 60, false, true, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}, QueryType.AudioPlaylists));
rowLoader.loadRows(mRows);
break;
@@ -214,7 +197,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
//On now
RecommendedProgramQuery onNow = new RecommendedProgramQuery();
onNow.setIsAiring(true);
onNow.setFields(new ItemFields[] {ItemFields.Overview, ItemFields.PrimaryImageAspectRatio, ItemFields.ChannelInfo});
onNow.setFields(new ItemFields[]{ItemFields.Overview, ItemFields.PrimaryImageAspectRatio, ItemFields.ChannelInfo});
onNow.setUserId(TvApp.getApplication().getCurrentUser().getId());
onNow.setImageTypeLimit(1);
onNow.setEnableTotalRecordCount(false);
@@ -261,7 +244,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
// Also get scheduled recordings for next 24 hours
final TimerQuery scheduled = new TimerQuery();
TvApp.getApplication().getApiClient().GetLiveTvTimersAsync(scheduled, new Response<TimerInfoDtoResult>(){
TvApp.getApplication().getApiClient().GetLiveTvTimersAsync(scheduled, new Response<TimerInfoDtoResult>() {
@Override
public void onResponse(TimerInfoDtoResult response) {
List<BaseItemDto> nearTimers = new ArrayList<>();
@@ -274,9 +257,9 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
programInfo = new BaseItemDto();
programInfo.setId(timer.getId());
programInfo.setChannelName(timer.getChannelName());
programInfo.setName(Utils.getSafeValue(timer.getName(),"Unknown"));
programInfo.setName(Utils.getSafeValue(timer.getName(), "Unknown"));
TvApp.getApplication().getLogger().Warn("No program info for timer %s. Creating one...", programInfo.getName());
programInfo.setType("Program");
programInfo.setBaseItemType(BaseItemType.Program);
programInfo.setTimerId(timer.getId());
programInfo.setSeriesTimerId(timer.getSeriesTimerId());
programInfo.setStartDate(timer.getStartDate());
@@ -309,11 +292,11 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
recordings.setFields(new ItemFields[]{ItemFields.Overview, ItemFields.PrimaryImageAspectRatio});
recordings.setUserId(TvApp.getApplication().getCurrentUser().getId());
recordings.setEnableImages(true);
mRows.add(new BrowseRowDef("Recent Recordings", recordings, 50));
mRows.add(new BrowseRowDef(mActivity.getString(R.string.lbl_recent_recordings), recordings, 50));
//All Recordings by group - will only be there for non-internal TV
RecordingGroupQuery recordingGroups = new RecordingGroupQuery();
recordingGroups.setUserId(TvApp.getApplication().getCurrentUser().getId());
mRows.add(new BrowseRowDef("All Recordings", recordingGroups));
mRows.add(new BrowseRowDef(mActivity.getString(R.string.lbl_all_recordings), recordingGroups));
rowLoader.loadRows(mRows);
//Now insert our smart rows
@@ -384,7 +367,7 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
ItemQuery rowQuery = new StdItemQuery();
rowQuery.setParentId(item.getId());
rowQuery.setUserId(userId);
rows.add(new BrowseRowDef(item.getName(), rowQuery, 60, new ChangeTriggerType[] {ChangeTriggerType.LibraryUpdated}));
rows.add(new BrowseRowDef(item.getName(), rowQuery, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}));
}
}
@@ -398,8 +381,6 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
});
break;
}
}
@Override
@@ -410,10 +391,10 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(TvApp.LIVE_TV_RECORDINGS_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_recorded_tv), R.drawable.recgroup));
gridRowAdapter.add(new GridButton(TvApp.LIVE_TV_RECORDINGS_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_recorded_tv), R.drawable.tile_port_record));
if (mApplication.canManageRecordings()) {
gridRowAdapter.add(new GridButton(TvApp.LIVE_TV_SCHEDULE_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_schedule), R.drawable.clock));
gridRowAdapter.add(new GridButton(TvApp.LIVE_TV_SERIES_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_series), R.drawable.seriestimerp));
gridRowAdapter.add(new GridButton(TvApp.LIVE_TV_SCHEDULE_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_schedule), R.drawable.tile_port_time));
gridRowAdapter.add(new GridButton(TvApp.LIVE_TV_SERIES_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_series), R.drawable.tile_port_series_timer));
}
mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));

View File

@@ -1,9 +1,9 @@
package org.jellyfin.androidtv.browsing;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1,9 +1,9 @@
package org.jellyfin.androidtv.browsing;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1,19 +1,19 @@
package org.jellyfin.androidtv.browsing;
import android.app.Fragment;
import androidx.fragment.app.Fragment;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.app.BackgroundManager;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
@@ -40,7 +40,7 @@ import java.util.Timer;
import java.util.TimerTask;
public class CustomBrowseFragment extends Fragment implements IRowLoader {
private RowsFragment mRowsFragment;
private RowsSupportFragment mRowsFragment;
private static final int BACKGROUND_UPDATE_DELAY = 100;
protected String MainTitle;
@@ -65,13 +65,13 @@ public class CustomBrowseFragment extends Fragment implements IRowLoader {
View root = inflater.inflate(R.layout.fragment_custom_browse, container, false);
// Inject the RowsFragment in the results container
// Inject the RowsSupportFragment in the results container
if (getChildFragmentManager().findFragmentById(R.id.rows_area) == null) {
mRowsFragment = new RowsFragment();
mRowsFragment = new RowsSupportFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.rows_area, mRowsFragment).commit();
} else {
mRowsFragment = (RowsFragment) getChildFragmentManager()
mRowsFragment = (RowsSupportFragment) getChildFragmentManager()
.findFragmentById(R.id.rows_area);
}

View File

@@ -1,24 +1,24 @@
package org.jellyfin.androidtv.browsing;
import android.app.Fragment;
import androidx.fragment.app.Fragment;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.annotation.Nullable;
import androidx.leanback.app.BackgroundManager;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.ClassPresenterSelector;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -60,6 +60,7 @@ import java.util.TimerTask;
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.BaseItemType;
import org.jellyfin.apiclient.model.entities.DisplayPreferences;
public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
@@ -95,7 +96,7 @@ public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
private SimpleTarget<Bitmap> mBackgroundTarget;
private DisplayMetrics mMetrics;
RowsFragment mRowsFragment;
RowsSupportFragment mRowsFragment;
protected CompositeClickedListener mClickedListener = new CompositeClickedListener();
protected CompositeSelectedListener mSelectedListener = new CompositeSelectedListener();
protected ArrayObjectAdapter mRowsAdapter;
@@ -112,7 +113,7 @@ public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
super.onCreate(savedInstanceState);
BaseItemDto item = new BaseItemDto();
item.setId(ItemListActivity.FAV_SONGS);
item.setType("Playlist");
item.setBaseItemType(BaseItemType.Playlist);
item.setIsFolder(true);
favSongsRowItem = new BaseRowItem(0, item);
@@ -135,13 +136,13 @@ public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
mSummary.setTypeface(roboto);
mSummary.setShadowLayer(5, 5, 5, Color.BLACK);
// Inject the RowsFragment in the results container
// Inject the RowsSupportFragment in the results container
if (getChildFragmentManager().findFragmentById(R.id.rowsFragment) == null) {
mRowsFragment = new RowsFragment();
mRowsFragment = new RowsSupportFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.rowsFragment, mRowsFragment).commit();
} else {
mRowsFragment = (RowsFragment) getChildFragmentManager()
mRowsFragment = (RowsSupportFragment) getChildFragmentManager()
.findFragmentById(R.id.rowsFragment);
}
@@ -326,14 +327,14 @@ public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
switch (itemTypeString) {
case "Movie":
gridRowAdapter.add(new GridButton(SUGGESTED, mApplication.getString(R.string.lbl_suggested), R.drawable.suggestions));
gridRowAdapter.add(new GridButton(SUGGESTED, mApplication.getString(R.string.lbl_suggested), R.drawable.tile_suggestions));
addStandardViewButtons(gridRowAdapter);
break;
case "MusicAlbum":
gridRowAdapter.add(new GridButton(ALBUMS, TvApp.getApplication().getString(R.string.lbl_albums), R.drawable.audio));
gridRowAdapter.add(new GridButton(ARTISTS, TvApp.getApplication().getString(R.string.lbl_artists), R.drawable.artists));
gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.genres));
gridRowAdapter.add(new GridButton(SEARCH, mApplication.getString(R.string.lbl_search), R.drawable.search));
gridRowAdapter.add(new GridButton(ALBUMS, TvApp.getApplication().getString(R.string.lbl_albums), R.drawable.tile_audio));
gridRowAdapter.add(new GridButton(ARTISTS, TvApp.getApplication().getString(R.string.lbl_artists), R.drawable.tile_artists));
gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.tile_genres));
gridRowAdapter.add(new GridButton(SEARCH, mApplication.getString(R.string.lbl_search), R.drawable.tile_search));
break;
default:
addStandardViewButtons(gridRowAdapter);
@@ -346,11 +347,11 @@ public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
}
protected void addStandardViewButtons(ArrayObjectAdapter gridRowAdapter) {
gridRowAdapter.add(new GridButton(GRID, TvApp.getApplication().getString(R.string.lbl_all_items), R.drawable.grid));
gridRowAdapter.add(new GridButton(BY_LETTER, mApplication.getString(R.string.lbl_by_letter), R.drawable.byletter));
gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.genres));
//gridRowAdapter.add(new GridButton(PERSONS, mApplication.getString(R.string.lbl_performers), R.drawable.actors));
gridRowAdapter.add(new GridButton(SEARCH, mApplication.getString(R.string.lbl_search), R.drawable.search));
gridRowAdapter.add(new GridButton(GRID, TvApp.getApplication().getString(R.string.lbl_all_items), R.drawable.tile_port_grid));
gridRowAdapter.add(new GridButton(BY_LETTER, mApplication.getString(R.string.lbl_by_letter), R.drawable.tile_letters));
gridRowAdapter.add(new GridButton(GENRES, mApplication.getString(R.string.lbl_genres), R.drawable.tile_genres));
//gridRowAdapter.add(new GridButton(PERSONS, mApplication.getString(R.string.lbl_performers), R.drawable.tile_actors));
gridRowAdapter.add(new GridButton(SEARCH, mApplication.getString(R.string.lbl_search), R.drawable.tile_search));
}
@@ -388,8 +389,8 @@ public class EnhancedBrowseFragment extends Fragment implements IRowLoader {
}
private void refreshCurrentItem() {
if (mCurrentItem != null && !"Photo".equals(mCurrentItem.getType()) && !"MusicArtist".equals(mCurrentItem.getType())
&& !"MusicAlbum".equals(mCurrentItem.getType()) && !"Playlist".equals(mCurrentItem.getType())) {
if (mCurrentItem != null && mCurrentItem.getBaseItemType() != BaseItemType.Photo && mCurrentItem.getBaseItemType() != BaseItemType.MusicArtist
&& mCurrentItem.getBaseItemType() != BaseItemType.MusicAlbum && mCurrentItem.getBaseItemType() != BaseItemType.Playlist) {
mCurrentItem.refresh(new EmptyResponse() {
@Override
public void onResponse() {

View File

@@ -9,6 +9,7 @@ import org.jellyfin.androidtv.querying.StdItemQuery;
import java.util.Arrays;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.entities.SortOrder;
import org.jellyfin.apiclient.model.livetv.RecordingQuery;
import org.jellyfin.apiclient.model.querying.ItemFields;
@@ -23,12 +24,12 @@ public class GenericFolderFragment extends EnhancedBrowseFragment {
}
private static String[] showSpecialViewTypes = new String[] {"CollectionFolder", "Folder", "UserView", "ChannelFolderItem"};
private static BaseItemType[] showSpecialViewTypes = new BaseItemType[] {BaseItemType.CollectionFolder, BaseItemType.Folder, BaseItemType.UserView, BaseItemType.ChannelFolderItem };
@Override
protected void setupQueries(IRowLoader rowLoader) {
if (mFolder.getType().equals("RecordingGroup")){
if (mFolder.getBaseItemType() == BaseItemType.RecordingGroup){
RecordingQuery query = new RecordingQuery();
query.setUserId(TvApp.getApplication().getCurrentUser().getId());
query.setGroupId(mFolder.getId());
@@ -38,14 +39,14 @@ public class GenericFolderFragment extends EnhancedBrowseFragment {
} else {
if (Utils.getSafeValue(mFolder.getChildCount(), 0) > 0 ||
mFolder.getType().equals("Channel") ||
mFolder.getType().equals("ChannelFolderItem") ||
mFolder.getType().equals("UserView") ||
mFolder.getType().equals("CollectionFolder")) {
boolean showSpecialViews = Arrays.asList(showSpecialViewTypes).contains(mFolder.getType()) && !"channels".equals(mFolder.getCollectionType());
mFolder.getBaseItemType() == BaseItemType.Channel ||
mFolder.getBaseItemType() == BaseItemType.ChannelFolderItem ||
mFolder.getBaseItemType() == BaseItemType.UserView ||
mFolder.getBaseItemType() == BaseItemType.CollectionFolder) {
boolean showSpecialViews = Arrays.asList(showSpecialViewTypes).contains(mFolder.getBaseItemType()) && !"channels".equals(mFolder.getCollectionType());
if (showSpecialViews) {
if (!mFolder.getType().equals("ChannelFolderItem")) {
if (mFolder.getBaseItemType() != BaseItemType.ChannelFolderItem) {
StdItemQuery resume = new StdItemQuery();
resume.setParentId(mFolder.getId());
resume.setLimit(50);

View File

@@ -1,405 +0,0 @@
package org.jellyfin.androidtv.browsing;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.widget.Toast;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.base.CustomMessage;
import org.jellyfin.androidtv.base.IMessageListener;
import org.jellyfin.androidtv.integration.RecommendationManager;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.livetv.LiveTvGuideActivity;
import org.jellyfin.androidtv.model.ChangeTriggerType;
import org.jellyfin.androidtv.model.DisplayPriorityType;
import org.jellyfin.androidtv.playback.AudioEventListener;
import org.jellyfin.androidtv.playback.MediaManager;
import org.jellyfin.androidtv.presentation.GridButtonPresenter;
import org.jellyfin.androidtv.presentation.ThemeManager;
import org.jellyfin.androidtv.querying.QueryType;
import org.jellyfin.androidtv.querying.StdItemQuery;
import org.jellyfin.androidtv.querying.ViewQuery;
import org.jellyfin.androidtv.settings.SettingsActivity;
import org.jellyfin.androidtv.startup.LogonCredentials;
import org.jellyfin.androidtv.startup.SelectUserActivity;
import org.jellyfin.androidtv.ui.GridButton;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
import java.io.IOException;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.entities.LocationType;
import org.jellyfin.apiclient.model.entities.SortOrder;
import org.jellyfin.apiclient.model.livetv.RecommendedProgramQuery;
import org.jellyfin.apiclient.model.livetv.RecordingQuery;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemFilter;
import org.jellyfin.apiclient.model.querying.ItemSortBy;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import org.jellyfin.apiclient.model.querying.LatestItemsQuery;
import org.jellyfin.apiclient.model.querying.NextUpQuery;
/**
* Created by Eric on 12/4/2014.
*/
public class HomeFragment extends StdBrowseFragment {
private static final int LOGOUT = 0;
private static final int SETTINGS = 1;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
MainTitle = this.getString(R.string.home_title);
super.onActivityCreated(savedInstanceState);
//Save last login so we can get back proper context on entry
try {
AuthenticationHelper.saveLoginCredentials(new LogonCredentials(TvApp.getApplication().getApiClient().getServerInfo(), TvApp.getApplication().getCurrentUser()), TvApp.CREDENTIALS_PATH);
} catch (IOException e) {
TvApp.getApplication().getLogger().ErrorException("Unable to save login creds", e);
}
//Init recommendations
RecommendationManager.init();
//Get auto bitrate
TvApp.getApplication().determineAutoBitrate();
//First time audio message
if (!mApplication.getSystemPrefs().getBoolean("syspref_audio_warned", false)) {
mApplication.getSystemPrefs().edit().putBoolean("syspref_audio_warned",true).apply();
new AlertDialog.Builder(mActivity)
.setTitle(mApplication.getString(R.string.lbl_audio_capabilitites))
.setMessage(mApplication.getString(R.string.msg_audio_warning))
.setPositiveButton(mApplication.getString(R.string.btn_got_it), null)
.setNegativeButton(mApplication.getString(R.string.btn_set_compatible_audio), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mApplication.getPrefs().edit().putString("pref_audio_option", "1").apply();
}
})
.setCancelable(false)
.show();
}
ThemeManager.showWelcomeMessage();
//Subscribe to Audio messages
MediaManager.addAudioEventListener(audioEventListener);
//Setup activity messages
mActivity.registerMessageListener(new IMessageListener() {
@Override
public void onMessageReceived(CustomMessage message) {
switch (message) {
case RefreshRows:
if (hasResumeRow()) {
refreshRows();
} else {
addContinueWatching();
}
break;
}
}
});
if (mApplication.getPrefs().getBoolean("pref_live_tv_mode",false)) {
//open guide activity and tell it to start last channel
Intent guide = new Intent(getActivity(), LiveTvGuideActivity.class);
guide.putExtra("loadLast", true);
startActivity(guide);
}
}
@Override
public void onResume() {
super.onResume();
//make sure rows have had a chance to be created
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
addNowPlaying();
//check for resume row and add if not there
if (!hasResumeRow()) addContinueWatching();
}
}, 750);
}
@Override
public void onDestroy() {
super.onDestroy();
MediaManager.removeAudioEventListener(audioEventListener);
}
@Override
protected void setupQueries(final IRowLoader rowLoader) {
//Peek at the first library item to determine our order
TvApp.getApplication().getApiClient().GetUserViews(TvApp.getApplication().getCurrentUser().getId(), new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
//First library and in-progress
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_library), new ViewQuery()));
//Special suggestions
String[] specialGenres = ThemeManager.getSpecialGenres();
if (specialGenres != null) {
StdItemQuery suggestions = new StdItemQuery();
suggestions.setIncludeItemTypes(new String[]{"Movie", "Series"});
suggestions.setGenres(specialGenres);
suggestions.setRecursive(true);
suggestions.setLimit(40);
suggestions.setEnableTotalRecordCount(false);
suggestions.setSortBy(new String[]{ItemSortBy.DatePlayed});
suggestions.setSortOrder(SortOrder.Ascending);
mRows.add(new BrowseRowDef(ThemeManager.getSuggestionTitle(), suggestions, 0, true, true, new ChangeTriggerType[]{}));
}
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_continue_watching), getResumeQuery(), 0, true, true, new ChangeTriggerType[]{ChangeTriggerType.MoviePlayback, ChangeTriggerType.TvPlayback, ChangeTriggerType.VideoQueueChange}, QueryType.ContinueWatching));
//Now others based on first library type
if (response.getTotalRecordCount() > 0) {
mApplication.setDisplayPriority("tvshows".equals(response.getItems()[0].getCollectionType()) ? DisplayPriorityType.TvShows : ("livetv".equals(response.getItems()[0].getCollectionType()) ? DisplayPriorityType.LiveTv : DisplayPriorityType.Movies));
switch (mApplication.getDisplayPriority()) {
case TvShows:
addNextUp();
addPremieres();
addLatestMovies();
addOnNow();
break;
case LiveTv:
addOnNow();
addNextUp();
addPremieres();
addLatestMovies();
break;
default:
addLatestMovies();
addNextUp();
addPremieres();
addOnNow();
}
}
// StdItemQuery latestMusic = new StdItemQuery();
// latestMusic.setIncludeItemTypes(new String[]{"MusicAlbum"});
// latestMusic.setRecursive(true);
// latestMusic.setLimit(50);
// latestMusic.setSortBy(new String[]{ItemSortBy.DateCreated});
// latestMusic.setSortOrder(SortOrder.Descending);
// mRowDef.add(new BrowseRowDef("Latest Albums", latestMusic, 0));
rowLoader.loadRows(mRows);
}
});
}
protected StdItemQuery getResumeQuery() {
StdItemQuery resumeItems = new StdItemQuery();
resumeItems.setMediaTypes(new String[] {"Video"});
resumeItems.setRecursive(true);
resumeItems.setImageTypeLimit(1);
resumeItems.setEnableTotalRecordCount(false);
resumeItems.setCollapseBoxSetItems(false);
resumeItems.setExcludeLocationTypes(new LocationType[] {LocationType.Virtual});
resumeItems.setLimit(50);
resumeItems.setFilters(new ItemFilter[]{ItemFilter.IsResumable});
resumeItems.setSortBy(new String[]{ItemSortBy.DatePlayed});
resumeItems.setSortOrder(SortOrder.Descending);
return resumeItems;
}
protected void addLatestMovies() {
LatestItemsQuery latestMovies = new LatestItemsQuery();
latestMovies.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
latestMovies.setIncludeItemTypes(new String[]{"Movie"});
latestMovies.setImageTypeLimit(1);
latestMovies.setLimit(50);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_latest_movies), latestMovies, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.MoviePlayback}));
}
protected void addNextUp() {
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
nextUpQuery.setImageTypeLimit(1);
nextUpQuery.setLimit(50);
nextUpQuery.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_next_up_tv), nextUpQuery, new ChangeTriggerType[] {ChangeTriggerType.TvPlayback}));
}
protected void addPremieres() {
if (mApplication.getPrefs().getBoolean("pref_enable_premieres", false)) {
StdItemQuery newQuery = new StdItemQuery(new ItemFields[]{ItemFields.DateCreated, ItemFields.PrimaryImageAspectRatio, ItemFields.Overview});
newQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
newQuery.setIncludeItemTypes(new String[]{"Episode"});
newQuery.setRecursive(true);
newQuery.setIsVirtualUnaired(false);
newQuery.setIsMissing(false);
newQuery.setImageTypeLimit(1);
newQuery.setFilters(new ItemFilter[]{ItemFilter.IsUnplayed});
newQuery.setSortBy(new String[]{ItemSortBy.DateCreated});
newQuery.setSortOrder(SortOrder.Descending);
newQuery.setEnableTotalRecordCount(false);
newQuery.setLimit(200);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_new_premieres), newQuery, 0, true, true, new ChangeTriggerType[] {ChangeTriggerType.TvPlayback}, QueryType.Premieres));
}
}
protected void addOnNow() {
if (TvApp.getApplication().getCurrentUser().getPolicy().getEnableLiveTvAccess()) {
RecommendedProgramQuery onNow = new RecommendedProgramQuery();
onNow.setIsAiring(true);
onNow.setFields(new ItemFields[] {ItemFields.Overview, ItemFields.PrimaryImageAspectRatio, ItemFields.ChannelInfo});
onNow.setUserId(TvApp.getApplication().getCurrentUser().getId());
onNow.setImageTypeLimit(1);
onNow.setEnableTotalRecordCount(false);
onNow.setLimit(20);
mRows.add(new BrowseRowDef(mApplication.getString(R.string.lbl_on_now), onNow));
//Latest Recordings
RecordingQuery recordings = new RecordingQuery();
recordings.setFields(new ItemFields[]{ItemFields.Overview, ItemFields.PrimaryImageAspectRatio});
recordings.setUserId(TvApp.getApplication().getCurrentUser().getId());
recordings.setEnableImages(true);
recordings.setLimit(40);
mRows.add(new BrowseRowDef(mActivity.getString(R.string.lbl_recordings), recordings));
}
}
protected boolean hasResumeRow() {
if (mRowsAdapter == null) return true;
for (int i = 0; i < mRowsAdapter.size(); i++) {
ListRow row = (ListRow)mRowsAdapter.get(i);
if (row.getAdapter() instanceof ItemRowAdapter && ((ItemRowAdapter)row.getAdapter()).getQueryType().equals(QueryType.ContinueWatching)) return true;
}
return false;
}
protected void addContinueWatching() {
//create the row and retrieve it to see if there are any before adding
final ItemRowAdapter resume = new ItemRowAdapter(getResumeQuery(), 0, true, true, mCardPresenter, mRowsAdapter, QueryType.ContinueWatching);
resume.setReRetrieveTriggers(new ChangeTriggerType[] {ChangeTriggerType.VideoQueueChange, ChangeTriggerType.TvPlayback, ChangeTriggerType.MoviePlayback});
final ListRow row = new ListRow(new HeaderItem(mApplication.getString(R.string.lbl_continue_watching)), resume);
resume.setRow(row);
resume.setRetrieveFinishedListener(new EmptyResponse() {
@Override
public void onResponse() {
mApplication.getLogger().Info("*** Continue watching retrieve finished: "+resume.size());
if (resume.size() > 0) {
mRowsAdapter.add(1, row);
}
}
});
resume.Retrieve();
}
protected AudioEventListener audioEventListener = new AudioEventListener() {
@Override
public void onQueueStatusChanged(boolean hasQueue) {
//remove on any change - it will re-add on resume
if (nowPlayingRow != null) {
mRowsAdapter.remove(nowPlayingRow);
nowPlayingRow = null;
}
}
};
protected ListRow nowPlayingRow;
protected void addNowPlaying() {
if (MediaManager.isPlayingAudio()) {
if (nowPlayingRow == null) {
nowPlayingRow = new ListRow(new HeaderItem(getString(R.string.lbl_now_playing)), MediaManager.getManagedAudioQueue());
mRowsAdapter.add(1, nowPlayingRow);
}
} else {
if (nowPlayingRow != null) {
mRowsAdapter.remove(nowPlayingRow);
nowPlayingRow = null;
}
}
}
@Override
protected void addAdditionalRows(ArrayObjectAdapter rowAdapter) {
super.addAdditionalRows(rowAdapter);
HeaderItem gridHeader = new HeaderItem(rowAdapter.size(), mApplication.getString(R.string.lbl_settings));
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter toolsRow = new ArrayObjectAdapter(mGridPresenter);
toolsRow.add(new GridButton(SETTINGS, mApplication.getString(R.string.lbl_app_settings), R.drawable.gears));
toolsRow.add(new GridButton(LOGOUT, mApplication.getString(R.string.lbl_logout) + TvApp.getApplication().getCurrentUser().getName(), R.drawable.logout));
rowAdapter.add(new ListRow(gridHeader, toolsRow));
}
@Override
protected void setupEventListeners() {
super.setupEventListeners();
mClickedListener.registerListener(new ItemViewClickedListener());
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof GridButton) {
switch (((GridButton) item).getId()) {
case LOGOUT:
TvApp app = TvApp.getApplication();
if (app.getIsAutoLoginConfigured()) {
// Present user selection
app.setLoginApiClient(app.getApiClient());
Intent userIntent = new Intent(getActivity(), SelectUserActivity.class);
userIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
getActivity().startActivity(userIntent);
}
getActivity().finish(); //don't actually log out because we handle it ourselves
break;
case SETTINGS:
Intent settings = new Intent(getActivity(), SettingsActivity.class);
getActivity().startActivity(settings);
break;
default:
Toast.makeText(getActivity(), item.toString(), Toast.LENGTH_SHORT)
.show();
break;
}
}
}
}
}

View File

@@ -1,15 +0,0 @@
package org.jellyfin.androidtv.browsing;
import android.os.Bundle;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.base.BaseActivity;
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
}
}

View File

@@ -0,0 +1,12 @@
package org.jellyfin.androidtv.browsing
import android.os.Bundle
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.base.BaseActivity
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
}
}

View File

@@ -17,16 +17,16 @@ package org.jellyfin.androidtv.browsing;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.BrowseFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.app.BackgroundManager;
import androidx.leanback.app.BrowseSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -67,8 +67,9 @@ import java.util.Timer;
import java.util.TimerTask;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.model.dto.BaseItemType;
public class StdBrowseFragment extends BrowseFragment implements IRowLoader {
public class StdBrowseFragment extends BrowseSupportFragment implements IRowLoader {
private static final String TAG = "StdBrowseFragment";
private static final int BACKGROUND_UPDATE_DELAY = 100;
@@ -90,7 +91,7 @@ public class StdBrowseFragment extends BrowseFragment implements IRowLoader {
private final Handler mHandler = new Handler();
private String mBackgroundUrl;
protected ArrayList<BrowseRowDef> mRows = new ArrayList<>();
CardPresenter mCardPresenter;
protected CardPresenter mCardPresenter;
protected boolean justLoaded = true;
@@ -262,7 +263,7 @@ public class StdBrowseFragment extends BrowseFragment implements IRowLoader {
}
protected void setupUIElements() {
if (ShowBadge) setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.logob400));
if (ShowBadge) setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.app_logo_transparent));
setTitle(MainTitle); // Badge, when set, takes precedent over title
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(true);
@@ -400,7 +401,7 @@ public class StdBrowseFragment extends BrowseFragment implements IRowLoader {
}
private void refreshCurrentItem() {
if (mCurrentItem != null && !mCurrentItem.getType().equals("UserView") && !mCurrentItem.getType().equals("CollectionFolder")) {
if (mCurrentItem != null && mCurrentItem.getBaseItemType() != BaseItemType.UserView && mCurrentItem.getBaseItemType() != BaseItemType.CollectionFolder) {
TvApp.getApplication().getLogger().Debug("Refresh item "+mCurrentItem.getFullName());
mCurrentItem.refresh(new EmptyResponse() {
@Override
@@ -452,9 +453,11 @@ public class StdBrowseFragment extends BrowseFragment implements IRowLoader {
mCurrentRow = (ListRow) row;
BaseRowItem rowItem = (BaseRowItem) item;
//mApplication.getLogger().Debug("Selected Item "+rowItem.getIndex() + " type: "+ (rowItem.getItemType().equals(BaseRowItem.ItemType.BaseItem) ? rowItem.getBaseItem().getType() : "other"));
ItemRowAdapter adapter = (ItemRowAdapter) ((ListRow)row).getAdapter();
adapter.loadMoreItemsIfNeeded(rowItem.getIndex());
if (((ListRow) row).getAdapter() instanceof ItemRowAdapter) {
//mApplication.getLogger().Debug("Selected Item "+rowItem.getIndex() + " type: "+ (rowItem.getItemType().equals(BaseRowItem.ItemType.BaseItem) ? rowItem.getBaseItem().getType() : "other"));
ItemRowAdapter adapter = (ItemRowAdapter) ((ListRow) row).getAdapter();
adapter.loadMoreItemsIfNeeded(rowItem.getIndex());
}
if (ShowFanart) {
mBackgroundUrl = rowItem.getBackdropImageUrl();

View File

@@ -17,16 +17,16 @@ package org.jellyfin.androidtv.browsing;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.app.BackgroundManager;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -74,6 +74,7 @@ import java.util.TimerTask;
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.BaseItemType;
import org.jellyfin.apiclient.model.entities.DisplayPreferences;
public class StdGridFragment extends HorizontalGridFragment implements IGridLoader {
@@ -370,7 +371,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
}
});
toolBar.addView(new ImageButton(getActivity(), R.drawable.sort, size, new View.OnClickListener() {
toolBar.addView(new ImageButton(getActivity(), R.drawable.ic_sort, size, new View.OnClickListener() {
@Override
public void onClick(View v) {
//Create sort menu
@@ -397,7 +398,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
}));
if (mRowDef.getQueryType() == QueryType.Items) {
mUnwatchedButton = new ImageButton(getActivity(), mGridAdapter.getFilters().isUnwatchedOnly() ? R.drawable.unwatchedred : R.drawable.unwatchedwhite, size, new View.OnClickListener() {
mUnwatchedButton = new ImageButton(getActivity(), mGridAdapter.getFilters().isUnwatchedOnly() ? R.drawable.ic_unwatch_red : R.drawable.ic_unwatch, size, new View.OnClickListener() {
@Override
public void onClick(View v) {
FilterOptions filters = mGridAdapter.getFilters();
@@ -411,7 +412,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
} else {
mGridAdapter.Retrieve();
}
mUnwatchedButton.setImageResource(filters.isUnwatchedOnly() ? R.drawable.unwatchedred : R.drawable.unwatchedwhite);
mUnwatchedButton.setImageResource(filters.isUnwatchedOnly() ? R.drawable.ic_unwatch_red : R.drawable.ic_unwatch);
}
@@ -419,7 +420,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
toolBar.addView(mUnwatchedButton);
}
mFavoriteButton =new ImageButton(getActivity(), mGridAdapter.getFilters().isFavoriteOnly() ? R.drawable.redheart : R.drawable.whiteheart, size, new View.OnClickListener() {
mFavoriteButton =new ImageButton(getActivity(), mGridAdapter.getFilters().isFavoriteOnly() ? R.drawable.ic_heart_red : R.drawable.ic_heart, size, new View.OnClickListener() {
@Override
public void onClick(View v) {
FilterOptions filters = mGridAdapter.getFilters();
@@ -433,13 +434,13 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
} else {
mGridAdapter.Retrieve();
}
mFavoriteButton.setImageResource(filters.isFavoriteOnly() ? R.drawable.redheart : R.drawable.whiteheart);
mFavoriteButton.setImageResource(filters.isFavoriteOnly() ? R.drawable.ic_heart_red : R.drawable.ic_heart);
}
});
toolBar.addView(mFavoriteButton);
mLetterButton = new ImageButton(getActivity(), R.drawable.jumpletter, size, new View.OnClickListener() {
mLetterButton = new ImageButton(getActivity(), R.drawable.ic_jump_letter, size, new View.OnClickListener() {
@Override
public void onClick(View v) {
//Open letter jump popup
@@ -448,14 +449,14 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
});
toolBar.addView(mLetterButton);
toolBar.addView(new ImageButton(getActivity(), R.drawable.search2, size, new View.OnClickListener() {
toolBar.addView(new ImageButton(getActivity(), R.drawable.ic_search, size, new View.OnClickListener() {
@Override
public void onClick(View v) {
TvApp.getApplication().showSearch(getActivity(), "music".equals(mFolder.getCollectionType()) || "MusicAlbum".equals(mFolder.getType()) || "MusicArtist".equals(mFolder.getType()));
TvApp.getApplication().showSearch(getActivity(), "music".equals(mFolder.getCollectionType()) || mFolder.getBaseItemType() == BaseItemType.MusicAlbum || mFolder.getBaseItemType() == BaseItemType.MusicArtist);
}
}));
toolBar.addView(new ImageButton(getActivity(), R.drawable.cog, size, new View.OnClickListener() {
toolBar.addView(new ImageButton(getActivity(), R.drawable.ic_settings, size, new View.OnClickListener() {
@Override
public void onClick(View v) {
mDisplayPrefsPopup.show(mDisplayPrefs, mFolder.getCollectionType());
@@ -482,7 +483,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
mPopup = new PopupWindow(layout, WIDTH, HEIGHT);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new BitmapDrawable()); // necessary for popup to dismiss
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mPopup.setAnimationStyle(R.style.PopupSlideInTop);
mJumplist = new JumpList(activity, new CharSelectedListener() {
@@ -589,8 +590,8 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
getGridPresenter().setPosition(MediaManager.getCurrentMediaPosition());
MediaManager.setCurrentMediaPosition(-1); // re-set so it doesn't mess with parent views
}
if (mCurrentItem != null && !"Photo".equals(mCurrentItem.getType()) && !"PhotoAlbum".equals(mCurrentItem.getType())
&& !"MusicArtist".equals(mCurrentItem.getType()) && !"MusicAlbum".equals(mCurrentItem.getType())) {
if (mCurrentItem != null && mCurrentItem.getBaseItemType() != BaseItemType.Photo && mCurrentItem.getBaseItemType() != BaseItemType.PhotoAlbum
&& mCurrentItem.getBaseItemType() != BaseItemType.MusicArtist && mCurrentItem.getBaseItemType() != BaseItemType.MusicAlbum) {
TvApp.getApplication().getLogger().Debug("Refresh item "+mCurrentItem.getFullName());
mCurrentItem.refresh(new EmptyResponse() {
@Override

View File

@@ -0,0 +1,126 @@
package org.jellyfin.androidtv.channels
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.tvprovider.media.tv.TvContractCompat.WatchNextPrograms
import androidx.tvprovider.media.tv.WatchNextProgram
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.TvApp
import org.jellyfin.androidtv.startup.StartupActivity
import org.jellyfin.androidtv.util.apiclient.getNextUpEpisodes
import org.jellyfin.apiclient.model.drawing.ImageFormat
import org.jellyfin.apiclient.model.dto.BaseItemDto
import org.jellyfin.apiclient.model.dto.ImageOptions
import org.jellyfin.apiclient.model.querying.ItemFields
import org.jellyfin.apiclient.model.querying.NextUpQuery
/**
* Manages channels on the android tv home screen
*
* More info: https://developer.android.com/training/tv/discovery/recommendations-channel
*/
class ChannelManager {
private companion object {
/**
* Amount of ticks found in a millisecond, used for calculation
*/
private const val TICKS_IN_MILLISECOND = 10000
}
private val application = TvApp.getApplication()
/**
* Check if the app can use Leanback features and is API level 26 or higher
*/
private val isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& application.packageManager.hasSystemFeature("android.software.leanback")
/**
* Update all channels for the currently authenticated user
*/
fun update() {
// Don't do anything if not supported
if (!isSupported) return
// Launch in separate coroutine scope
GlobalScope.launch {
updateWatchNext()
}
}
/**
* Updates the "watch next" row with new and unfinished episodes
* does not include movies, music or other types of media
*/
private suspend fun updateWatchNext() = withContext(Dispatchers.Default) {
// Delete current items
application.contentResolver.delete(WatchNextPrograms.CONTENT_URI, null, null)
// Get user or return if no user is found (not authenticated)
val user = application.currentUser ?: return@withContext
// Get new items
val response = application.apiClient.getNextUpEpisodes(NextUpQuery().apply {
userId = user.id
imageTypeLimit = 1
limit = 10
fields = arrayOf(ItemFields.DateCreated)
})
// Add new items
response?.items?.forEach { item ->
application.contentResolver.insert(
WatchNextPrograms.CONTENT_URI,
getBaseItemAsWatchNextProgram(item).toContentValues()
)
}
}
/**
* Convert [BaseItemDto] to [WatchNextProgram]
*
* Assumes the item type is "episode"
*/
private fun getBaseItemAsWatchNextProgram(item: BaseItemDto) = WatchNextProgram.Builder().apply {
setInternalProviderId(item.id)
setType(WatchNextPrograms.TYPE_TV_EPISODE)
setTitle("${item.seriesName} - ${item.name}")
// Poster image
setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
setPosterArtUri(Uri.parse(application.apiClient.GetImageUrl(item, ImageOptions().apply {
format = ImageFormat.Png
height = 288
width = 512
})))
// Use date created or fallback to current time if unavailable
setLastEngagementTimeUtcMillis(item.dateCreated?.time ?: System.currentTimeMillis())
when {
// User has started playing the episode
item.canResume -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
setLastPlaybackPositionMillis((item.resumePositionTicks / TICKS_IN_MILLISECOND).toInt())
setDurationMillis((item.runTimeTicks / TICKS_IN_MILLISECOND).toInt())
}
// First episode of the season
item.indexNumber == 0 -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEW)
}
// Default
else -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEXT)
}
}
// Set intent to open the episode
setIntent(Intent(application, StartupActivity::class.java).apply {
putExtra("ItemId", item.id)
})
}.build()
}

View File

@@ -0,0 +1,37 @@
package org.jellyfin.androidtv.constants;
public class CodecTypes {
/* Audio Codec Types */
public static final String AAC = "aac";
public static final String AAC_LATM = "aac_latm";
public static final String AC3 = "ac3";
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 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 TRUEHD = "truehd";
public static final String VORBIS = "vorbis";
public static final String WAV = "wav";
public static final String WMA = "wma";
public static final String WMAV2 = "wmav2";
/* Video Codec Types */
public static final String AVC = "avc";
public static final String H264 = "h264";
public static final String H265 = "h265";
public static final String HEVC = "hevc";
public static final String MPEG2 = "mpeg2";
public static final String MPEG2VIDEO = "mpeg2video";
public static final String MPEG4 = "mpeg4";
public static final String VP8 = "vp8";
public static final String VP9 = "vp9";
public static final String VPX = "vpx";
}

View File

@@ -0,0 +1,29 @@
package org.jellyfin.androidtv.constants;
public class ContainerTypes {
public static final String _3GP = "3gp";
public static final String _3GPP = "3gpp";
public static final String ASF = "asf";
public static final String AVI = "avi";
public static final String DVR_MS = "dvr-ms";
public static final String M2TS = "m2ts";
public static final String M2V = "m2v";
public static final String M4V = "m4v";
public static final String MKV = "mkv";
public static final String MOV = "mov";
public static final String MP4 = "mp4";
public static final String MPEG = "mpeg";
public static final String MPEGTS = "mpegts";
public static final String MPG = "mpg";
public static final String OGA = "oga";
public static final String OGG = "ogg";
public static final String OGM = "ogm";
public static final String OGV = "ogv";
public static final String TS = "ts";
public static final String VOB = "vob";
public static final String WEBM = "webm";
public static final String WEBMA = "webma";
public static final String WMV = "wmv";
public static final String WTV = "wtv";
public static final String XVID = "xvid";
}

View File

@@ -0,0 +1,30 @@
package org.jellyfin.androidtv.constants;
// Names used in jellyfin-web
// Only uses those that are configurable in web settings
public enum HomeSectionType {
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");
public static HomeSectionType getByName(String name) {
for (HomeSectionType type : HomeSectionType.values()) {
if (type.name.equalsIgnoreCase(name)) return type;
}
return null;
}
public final String name;
HomeSectionType(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,5 @@
package org.jellyfin.androidtv.constants;
public class MediaTypes {
public static final String HLS = "hls";
}

View File

@@ -1,13 +1,14 @@
package org.jellyfin.androidtv.details;
import android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
public class ExpandedTextActivity extends Activity {
import androidx.fragment.app.FragmentActivity;
public class ExpandedTextActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@@ -11,17 +11,17 @@ import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.app.BackgroundManager;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.ClassPresenterSelector;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.MenuItem;
@@ -77,6 +77,7 @@ 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.BaseItemPerson;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.ImageOptions;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
@@ -117,7 +118,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
private Calendar mLastUpdated;
private String mPrevItemId;
private RowsFragment mRowsFragment;
private RowsSupportFragment mRowsFragment;
private ArrayObjectAdapter mRowsAdapter;
private MyDetailsOverviewRowPresenter mDorPresenter;
@@ -145,14 +146,13 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mActivity = this;
BackgroundManager backgroundManager = BackgroundManager.getInstance(this);
backgroundManager.attach(getWindow());
backgroundManager.setDimLayer(getDrawable(R.drawable.left_fade));
mBackgroundTarget = new MyPicassoBackgroundManagerTarget(backgroundManager);
mMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
mRowsFragment = new RowsFragment();
getFragmentManager().beginTransaction().add(R.id.rowsFragment, mRowsFragment).commit();
mRowsFragment = new RowsSupportFragment();
getSupportFragmentManager().beginTransaction().add(R.id.rowsFragment, mRowsFragment).commit();
mRowsFragment.setOnItemViewClickedListener(new ItemViewClickedListener());
mRowsFragment.setOnItemViewSelectedListener(new ItemViewSelectedListener());
@@ -169,7 +169,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
registerMessageListener(new IMessageListener() {
@Override
public void onMessageReceived(CustomMessage message) {
if (message == CustomMessage.ActionComplete && mSeriesTimerInfo != null && "SeriesTimer".equals(mBaseItem.getType())) {
if (message == CustomMessage.ActionComplete && mSeriesTimerInfo != null && mBaseItem.getBaseItemType() == BaseItemType.SeriesTimer) {
//update info
mApplication.getApiClient().GetLiveTvSeriesTimerAsync(mSeriesTimerInfo.getId(), new Response<SeriesTimerInfoDto>() {
@Override
@@ -206,11 +206,11 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
rotateBackdrops();
//Update information that may have changed - delay slightly to allow changes to take on the server
if (mApplication.getLastPlayback().after(mLastUpdated) && !"MusicArtist".equals(mBaseItem.getType())) {
if (mApplication.getLastPlayback().after(mLastUpdated) && mBaseItem.getBaseItemType() != BaseItemType.MusicArtist) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if ("Episode".equals(mBaseItem.getType()) && mApplication.getLastPlayback().after(mLastUpdated) && mApplication.getLastPlayedItem() != null && !mBaseItem.getId().equals(mApplication.getLastPlayedItem().getId()) && "Episode".equals(mApplication.getLastPlayedItem().getType())) {
if (mBaseItem.getBaseItemType() == BaseItemType.Episode && mApplication.getLastPlayback().after(mLastUpdated) && mApplication.getLastPlayedItem() != null && !mBaseItem.getId().equals(mApplication.getLastPlayedItem().getId()) && mApplication.getLastPlayedItem().getBaseItemType() == BaseItemType.Episode) {
mApplication.getLogger().Info("Re-loading after new episode playback");
loadItem(mApplication.getLastPlayedItem().getId());
mApplication.setLastPlayedItem(null); //blank this out so a detail screen we back up to doesn't also do this
@@ -222,7 +222,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
if (!isFinishing()) {
mBaseItem = response;
if (mResumeButton != null) {
mResumeButton.setVisibility(("Series".equals(mBaseItem.getType()) && ! mBaseItem.getUserData().getPlayed()) || response.getCanResume() ? View.VISIBLE : View.GONE);
mResumeButton.setVisibility((mBaseItem.getBaseItemType() == BaseItemType.Series && ! mBaseItem.getUserData().getPlayed()) || response.getCanResume() ? View.VISIBLE : View.GONE);
if (response.getCanResume()){
mResumeButton.setText(String.format(getString(R.string.lbl_resume_from), TimeUtils.formatMillis((response.getUserData().getPlaybackPositionTicks()/10000) - mApplication.getResumePreroll())));
}
@@ -263,7 +263,6 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mCurrentItem != null) {
return KeyProcessor.HandleKey(keyCode, mCurrentItem, this) || super.onKeyUp(keyCode, event);
} else if ((keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) && BaseItemUtils.canPlay(mBaseItem)) {
//default play action
Long pos = mBaseItem.getUserData().getPlaybackPositionTicks() / 10000;
@@ -294,8 +293,25 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
}
private static String[] buttonTypes = new String[] {"Episode","Movie","Series","Season","Folder","Video","Recording","Program","ChannelVideoItem","Trailer","MusicArtist","Person","MusicVideo","SeriesTimer"};
private static List<String> buttonTypeList = Arrays.asList(buttonTypes);
private static BaseItemType[] buttonTypes = new BaseItemType[] {
BaseItemType.Episode,
BaseItemType.Movie,
BaseItemType.Series,
BaseItemType.Season,
BaseItemType.Folder,
BaseItemType.Video,
BaseItemType.Recording,
BaseItemType.Program,
BaseItemType.ChannelVideoItem,
BaseItemType.Trailer,
BaseItemType.MusicArtist,
BaseItemType.Person,
BaseItemType.MusicVideo,
BaseItemType.SeriesTimer
};
private static List<BaseItemType> buttonTypeList = Arrays.asList(buttonTypes);
private static String[] directPlayableTypes = new String[] {"Episode","Movie","Video","Recording","Program"};
private static List<String> directPlayableTypeList = Arrays.asList(directPlayableTypes);
@@ -311,7 +327,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
private void updateWatched() {
if (mWatchedToggleButton != null && mBaseItem != null && mBaseItem.getUserData() != null && !isFinishing()) {
mWatchedToggleButton.setImageResource(mBaseItem.getUserData().getPlayed() ? R.drawable.redcheck : R.drawable.whitecheck);
mWatchedToggleButton.setImageResource(mBaseItem.getUserData().getPlayed() ? R.drawable.ic_watch_red : R.drawable.ic_watch);
}
}
@@ -333,7 +349,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
BaseItemDto item = new BaseItemDto();
item.setId(mSeriesTimerInfo.getId());
item.setSeriesTimerId(mSeriesTimerInfo.getId());
item.setType("SeriesTimer");
item.setBaseItemType(BaseItemType.SeriesTimer);
item.setName(mSeriesTimerInfo.getName());
item.setOverview(BaseItemUtils.getSeriesOverview(mSeriesTimerInfo));
@@ -348,7 +364,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
@Override
public void setRecTimer(String id) {
mProgramInfo.setTimerId(id);
if (mRecordButton != null) mRecordButton.setImageResource(id == null ? R.drawable.recwhite : R.drawable.rec);
if (mRecordButton != null) mRecordButton.setImageResource(id == null ? R.drawable.ic_record : R.drawable.ic_record_red);
}
private int posterWidth;
@@ -357,7 +373,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
@Override
public void setRecSeriesTimer(String id) {
if (mProgramInfo != null) mProgramInfo.setSeriesTimerId(id);
if (mRecSeriesButton != null) mRecSeriesButton.setImageResource(id == null ? R.drawable.recserieswhite : R.drawable.recseries);
if (mRecSeriesButton != null) mRecSeriesButton.setImageResource(id == null ? R.drawable.ic_record_series : R.drawable.ic_record_series_red);
if (mSeriesSettingsButton != null) mSeriesSettingsButton.setVisibility(id == null ? View.GONE : View.VISIBLE);
}
@@ -370,7 +386,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
// Figure image size
Double aspect = ImageUtils.getImageAspectRatio(item, false);
posterHeight = aspect > 1 ? Utils.convertDpToPixel(mActivity, 160) : Utils.convertDpToPixel(mActivity, "Person".equals(item.getType()) || "MusicArtist".equals(item.getType()) ? 300 : 200);
posterHeight = aspect > 1 ? Utils.convertDpToPixel(mActivity, 160) : Utils.convertDpToPixel(mActivity, item.getBaseItemType() == BaseItemType.Person || item.getBaseItemType() == BaseItemType.MusicArtist ? 300 : 200);
posterWidth = (int)((aspect) * posterHeight);
if (posterHeight < 10) posterWidth = Utils.convertDpToPixel(mActivity, 150); //Guard against zero size images causing picasso to barf
@@ -379,9 +395,9 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mDetailsOverviewRow = new MyDetailsOverviewRow(item);
mDetailsOverviewRow.setSummary(item.getOverview());
switch (item.getType()) {
case "Person":
case "MusicArtist":
switch (item.getBaseItemType()) {
case Person:
case MusicArtist:
mDetailsOverviewRow.setSummarySubTitle("");
break;
default:
@@ -389,7 +405,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
BaseItemPerson director = BaseItemUtils.getFirstPerson(item, PersonType.Director);
InfoItem firstRow;
if ("Series".equals(item.getType())) {
if (item.getBaseItemType() == BaseItemType.Series) {
firstRow = new InfoItem(
getString(R.string.lbl_seasons),
Utils.getSafeValue(item.getChildCount(), 0).toString());
@@ -489,9 +505,9 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
protected void addAdditionalRows(ArrayObjectAdapter adapter) {
TvApp.getApplication().getLogger().Debug("Item type: "+mBaseItem.getType());
switch (mBaseItem.getType()) {
case "Movie":
TvApp.getApplication().getLogger().Debug("Item type: " + mBaseItem.getBaseItemType());
switch (mBaseItem.getBaseItemType()) {
case Movie:
//Cast/Crew
if (mBaseItem.getPeople() != null && mBaseItem.getPeople().length > 0) {
@@ -528,7 +544,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
addInfoRows(adapter);
break;
case "Trailer":
case Trailer:
//Cast/Crew
if (mBaseItem.getPeople() != null && mBaseItem.getPeople().length > 0) {
@@ -547,7 +563,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
addItemRow(adapter, similarTrailerAdapter, 4, getString(R.string.lbl_more_like_this));
addInfoRows(adapter);
break;
case "Person":
case Person:
ItemQuery personMovies = new ItemQuery();
personMovies.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio});
@@ -568,7 +584,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
addItemRow(adapter, personSeriesAdapter, 1, mApplication.getString(R.string.lbl_tv_series));
break;
case "MusicArtist":
case MusicArtist:
ItemQuery artistAlbums = new ItemQuery();
artistAlbums.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio});
@@ -580,7 +596,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
addItemRow(adapter, artistAlbumsAdapter, 0, mApplication.getString(R.string.lbl_albums));
break;
case "Series":
case Series:
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
nextUpQuery.setSeriesId(mBaseItem.getId());
@@ -617,7 +633,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
addItemRow(adapter, similarAdapter, 4, getString(R.string.lbl_more_like_this));
break;
case "Episode":
case Episode:
if (mBaseItem.getSeasonId() != null && mBaseItem.getIndexNumber() != null) {
StdItemQuery nextEpisodes = new StdItemQuery();
nextEpisodes.setParentId(mBaseItem.getSeasonId());
@@ -650,7 +666,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
addInfoRows(adapter);
break;
case "SeriesTimer":
case SeriesTimer:
TimerQuery scheduled = new TimerQuery();
scheduled.setSeriesTimerId(mSeriesTimerInfo.getId());
TvManager.getScheduleRowsAsync(scheduled, new CardPresenter(true), adapter, new Response<Integer>());
@@ -678,7 +694,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
private void updateInfo(BaseItemDto item) {
if (buttonTypeList.contains(item.getType())) addButtons(BUTTON_SIZE);
if (buttonTypeList.contains(item.getBaseItemType())) addButtons(BUTTON_SIZE);
// updatePlayedDate();
//
updateBackground(ImageUtils.getBackdropImageUrl(item, TvApp.getApplication().getApiClient(), true));
@@ -707,10 +723,10 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
private String getEndTime() {
if (mBaseItem != null && !"MusicArtist".equals(mBaseItem.getType()) && !"Person".equals(mBaseItem.getType())) {
if (mBaseItem != null && mBaseItem.getBaseItemType() != BaseItemType.MusicArtist && mBaseItem.getBaseItemType() != BaseItemType.Person) {
Long runtime = Utils.getSafeValue(mBaseItem.getRunTimeTicks(), mBaseItem.getOriginalRunTimeTicks());
if (runtime != null && runtime > 0) {
long endTimeTicks = "Program".equals(mBaseItem.getType()) && mBaseItem.getEndDate() != null ? TimeUtils.convertToLocalDate(mBaseItem.getEndDate()).getTime() : System.currentTimeMillis() + runtime / 10000;
long endTimeTicks = mBaseItem.getBaseItemType() == BaseItemType.Program && mBaseItem.getEndDate() != null ? TimeUtils.convertToLocalDate(mBaseItem.getEndDate()).getTime() : System.currentTimeMillis() + runtime / 10000;
if (mBaseItem.getCanResume()) {
endTimeTicks = System.currentTimeMillis() + ((runtime - mBaseItem.getUserData().getPlaybackPositionTicks()) / 10000);
return android.text.format.DateFormat.getTimeFormat(this).format(new Date(endTimeTicks));
@@ -724,7 +740,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
private void addItemToQueue() {
if ("Audio".equals(mBaseItem.getType())) {
if (mBaseItem.getBaseItemType() == BaseItemType.Audio) {
MediaManager.addToAudioQueue(Arrays.asList(mBaseItem));
} else {
@@ -738,7 +754,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
favButton.setImageResource(response.getIsFavorite() ? R.drawable.redheart : R.drawable.whiteheart);
favButton.setImageResource(response.getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart);
TvApp.getApplication().setLastFavoriteUpdate(System.currentTimeMillis());
}
});
@@ -792,10 +808,20 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
private TextUnderButton moreButton;
private void addButtons(int buttonSize) {
mResumeButton = new TextUnderButton(this, R.drawable.resume, buttonSize, 2, "Series".equals(mBaseItem.getType()) ? getString(R.string.lbl_play_next_up) : String.format(getString(R.string.lbl_resume_from), TimeUtils.formatMillis((mBaseItem.getUserData().getPlaybackPositionTicks()/10000) - mApplication.getResumePreroll())), new View.OnClickListener() {
String buttonLabel;
if (mBaseItem.getBaseItemType() == BaseItemType.Series || mBaseItem.getBaseItemType() == BaseItemType.SeriesTimer) {
buttonLabel = getString(R.string.lbl_play_next_up);
} else {
long startPos = 0;
if (mBaseItem.getCanResume()) {
startPos = (mBaseItem.getUserData().getPlaybackPositionTicks()/10000) - mApplication.getResumePreroll();
}
buttonLabel = String.format(getString(R.string.lbl_resume_from), TimeUtils.formatMillis(startPos));
}
mResumeButton = new TextUnderButton(this, R.drawable.ic_resume, buttonSize, 2, buttonLabel, new View.OnClickListener() {
@Override
public void onClick(View v) {
if ("Series".equals(mBaseItem.getType())) {
if (mBaseItem.getBaseItemType() == BaseItemType.Series) {
//play next up
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
@@ -827,18 +853,24 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
if (BaseItemUtils.canPlay(mBaseItem)) {
mDetailsOverviewRow.addAction(mResumeButton);
mResumeButton.setVisibility(("Series".equals(mBaseItem.getType()) && ! mBaseItem.getUserData().getPlayed()) || (mBaseItem.getCanResume() ) ? View.VISIBLE : View.GONE);
boolean resumeButtonVisible = (mBaseItem.getBaseItemType() == BaseItemType.Series && !mBaseItem.getUserData().getPlayed()) || (mBaseItem.getCanResume());
mResumeButton.setVisibility(resumeButtonVisible ? View.VISIBLE : View.GONE);
TextUnderButton play = new TextUnderButton(this, R.drawable.play, buttonSize, 2, getString(BaseItemUtils.isLiveTv(mBaseItem) ? R.string.lbl_tune_to_channel : mBaseItem.getIsFolderItem() ? R.string.lbl_play_all : R.string.lbl_play), new View.OnClickListener() {
TextUnderButton play = new TextUnderButton(this, R.drawable.ic_play, buttonSize, 2, getString(BaseItemUtils.isLiveTv(mBaseItem) ? R.string.lbl_tune_to_channel : mBaseItem.getIsFolderItem() ? R.string.lbl_play_all : R.string.lbl_play), new View.OnClickListener() {
@Override
public void onClick(View v) {
play(mBaseItem, 0, false);
}
});
mDetailsOverviewRow.addAction(play);
if (resumeButtonVisible) {
mResumeButton.requestFocus();
} else {
play.requestFocus();
}
if (!mBaseItem.getIsFolderItem() && !BaseItemUtils.isLiveTv(mBaseItem)) {
queueButton = new TextUnderButton(this, R.drawable.addtoqueue, buttonSize, 2, getString(R.string.lbl_add_to_queue), new View.OnClickListener() {
queueButton = new TextUnderButton(this, R.drawable.ic_add, buttonSize, 2, getString(R.string.lbl_add_to_queue), new View.OnClickListener() {
@Override
public void onClick(View v) {
addItemToQueue();
@@ -848,7 +880,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
if (mBaseItem.getIsFolderItem()) {
TextUnderButton shuffle = new TextUnderButton(this, R.drawable.shuffle, buttonSize, 2, getString(R.string.lbl_shuffle_all), new View.OnClickListener() {
TextUnderButton shuffle = new TextUnderButton(this, R.drawable.ic_shuffle, buttonSize, 2, getString(R.string.lbl_shuffle_all), new View.OnClickListener() {
@Override
public void onClick(View v) {
play(mBaseItem, 0, true);
@@ -857,8 +889,8 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mDetailsOverviewRow.addAction(shuffle);
}
if ("MusicArtist".equals(mBaseItem.getType())) {
TextUnderButton imix = new TextUnderButton(this, R.drawable.mix, buttonSize, getString(R.string.lbl_instant_mix), new View.OnClickListener() {
if (mBaseItem.getBaseItemType() == BaseItemType.MusicArtist) {
TextUnderButton imix = new TextUnderButton(this, R.drawable.ic_mix, buttonSize, getString(R.string.lbl_instant_mix), new View.OnClickListener() {
@Override
public void onClick(View v) {
Utils.beep();
@@ -871,7 +903,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
if (mBaseItem.getLocalTrailerCount() != null && mBaseItem.getLocalTrailerCount() > 0) {
TextUnderButton trailer = new TextUnderButton(this, R.drawable.trailer, buttonSize, getString(R.string.lbl_play_trailers), new View.OnClickListener() {
TextUnderButton trailer = new TextUnderButton(this, R.drawable.ic_trailer, buttonSize, getString(R.string.lbl_play_trailers), new View.OnClickListener() {
@Override
public void onClick(View v) {
TvApp.getApplication().getApiClient().GetLocalTrailersAsync(TvApp.getApplication().getCurrentUser().getId(), mBaseItem.getId(), new Response<BaseItemDto[]>() {
@@ -896,7 +928,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
if (mProgramInfo != null && mApplication.canManageRecordings()) {
if (TimeUtils.convertToLocalDate(mBaseItem.getEndDate()).getTime() > System.currentTimeMillis()) {
//Record button
mRecordButton = new TextUnderButton(this, mProgramInfo.getTimerId() != null ? R.drawable.rec : R.drawable.recwhite, buttonSize, 4, getString(R.string.lbl_record), new View.OnClickListener() {
mRecordButton = new TextUnderButton(this, mProgramInfo.getTimerId() != null ? R.drawable.ic_record_red : R.drawable.ic_record, buttonSize, 4, getString(R.string.lbl_record), new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mProgramInfo.getTimerId() == null) {
@@ -957,7 +989,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
if (mProgramInfo.getIsSeries() != null && mProgramInfo.getIsSeries()) {
mRecSeriesButton= new TextUnderButton(this, mProgramInfo.getSeriesTimerId() != null ? R.drawable.recseries : R.drawable.recserieswhite, buttonSize, 4, getString(R.string.lbl_record_series), new View.OnClickListener() {
mRecSeriesButton= new TextUnderButton(this, mProgramInfo.getSeriesTimerId() != null ? R.drawable.ic_record_series_red : R.drawable.ic_record_series, buttonSize, 4, getString(R.string.lbl_record_series), new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mProgramInfo.getSeriesTimerId() == null) {
@@ -1026,7 +1058,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mDetailsOverviewRow.addAction(mRecSeriesButton);
mSeriesSettingsButton = new TextUnderButton(this, R.drawable.cog, buttonSize, 2, getString(R.string.lbl_series_settings), new View.OnClickListener() {
mSeriesSettingsButton = new TextUnderButton(this, R.drawable.ic_settings, buttonSize, 2, getString(R.string.lbl_series_settings), new View.OnClickListener() {
@Override
public void onClick(View v) {
showRecordingOptions(mProgramInfo.getSeriesTimerId(), mProgramInfo, true);
@@ -1041,13 +1073,13 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
UserItemDataDto userData = mBaseItem.getUserData();
if (userData != null && mProgramInfo == null) {
if (!"MusicArtist".equals(mBaseItem.getType()) && !"Person".equals(mBaseItem.getType())) {
mWatchedToggleButton = new TextUnderButton(this, userData.getPlayed() ? R.drawable.redcheck : R.drawable.whitecheck, buttonSize, getString(R.string.lbl_toggle_watched), markWatchedListener);
if (mBaseItem.getBaseItemType() != BaseItemType.MusicArtist && mBaseItem.getBaseItemType() != BaseItemType.Person) {
mWatchedToggleButton = new TextUnderButton(this, userData.getPlayed() ? R.drawable.ic_watch_red : R.drawable.ic_watch, buttonSize, getString(R.string.lbl_watched), markWatchedListener);
mDetailsOverviewRow.addAction(mWatchedToggleButton);
}
//Favorite
favButton = new TextUnderButton(this, userData.getIsFavorite() ? R.drawable.redheart : R.drawable.whiteheart, buttonSize, 2, getString(R.string.lbl_toggle_favorite), new View.OnClickListener() {
favButton = new TextUnderButton(this, userData.getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart, buttonSize, 2, getString(R.string.lbl_favorite), new View.OnClickListener() {
@Override
public void onClick(final View v) {
toggleFavorite();
@@ -1056,9 +1088,9 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mDetailsOverviewRow.addAction(favButton);
}
if ("Episode".equals(mBaseItem.getType()) && mBaseItem.getSeriesId() != null) {
if (mBaseItem.getBaseItemType() == BaseItemType.Episode && mBaseItem.getSeriesId() != null) {
//add the prev button first so it will be there in proper position - we'll show it later if needed
mPrevButton = new TextUnderButton(this, R.drawable.prev, buttonSize, 3, getString(R.string.lbl_previous_episode), new View.OnClickListener() {
mPrevButton = new TextUnderButton(this, R.drawable.ic_previous_episode, buttonSize, 3, getString(R.string.lbl_previous_episode), new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPrevItemId != null) {
@@ -1092,7 +1124,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
}
});
goToSeriesButton = new TextUnderButton(this, R.drawable.tvicon, buttonSize, getString(R.string.lbl_goto_series), new View.OnClickListener() {
goToSeriesButton = new TextUnderButton(this, R.drawable.ic_tv, buttonSize, getString(R.string.lbl_goto_series), new View.OnClickListener() {
@Override
public void onClick(View v) {
gotoSeries();
@@ -1101,9 +1133,9 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mDetailsOverviewRow.addAction(goToSeriesButton);
}
if (("Recording".equals(mBaseItem.getType()) && TvApp.getApplication().getCurrentUser().getPolicy().getEnableLiveTvManagement() && mBaseItem.getCanDelete()) ||
(("Movie".equals(mBaseItem.getType()) || "Episode".equals(mBaseItem.getType()) || "Video".equals(mBaseItem.getType())) && TvApp.getApplication().getCurrentUser().getPolicy().getEnableContentDeletion())) {
deleteButton = new TextUnderButton(this, R.drawable.trash, buttonSize, getString(R.string.lbl_delete), new View.OnClickListener() {
if ((mBaseItem.getBaseItemType() == BaseItemType.Recording && TvApp.getApplication().getCurrentUser().getPolicy().getEnableLiveTvManagement() && mBaseItem.getCanDelete()) ||
((mBaseItem.getBaseItemType() == BaseItemType.Movie || mBaseItem.getBaseItemType() == BaseItemType.Episode || mBaseItem.getBaseItemType() == BaseItemType.Video) && TvApp.getApplication().getCurrentUser().getPolicy().getEnableContentDeletion())) {
deleteButton = new TextUnderButton(this, R.drawable.ic_trash, buttonSize, getString(R.string.lbl_delete), new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteItem();
@@ -1112,9 +1144,9 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
mDetailsOverviewRow.addAction(deleteButton);
}
if (mSeriesTimerInfo != null && "SeriesTimer".equals(mBaseItem.getType())) {
if (mSeriesTimerInfo != null && mBaseItem.getBaseItemType() == BaseItemType.SeriesTimer) {
//Settings
mDetailsOverviewRow.addAction(new TextUnderButton(this, R.drawable.cog, buttonSize, getString(R.string.lbl_series_settings), new View.OnClickListener() {
mDetailsOverviewRow.addAction(new TextUnderButton(this, R.drawable.ic_settings, buttonSize, getString(R.string.lbl_series_settings), new View.OnClickListener() {
@Override
public void onClick(View v) {
//show recording options
@@ -1124,7 +1156,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
//Delete
final Activity activity = this;
TextUnderButton del = new TextUnderButton(this, R.drawable.trash, buttonSize, getString(R.string.lbl_cancel_series), new View.OnClickListener() {
TextUnderButton del = new TextUnderButton(this, R.drawable.ic_trash, buttonSize, getString(R.string.lbl_cancel_series), new View.OnClickListener() {
@Override
public void onClick(View v) {
new AlertDialog.Builder(activity)
@@ -1198,7 +1230,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
moreButton.setVisibility(View.GONE);
mDetailsOverviewRow.addAction(moreButton);
if (!"Episode".equals(mBaseItem.getType())) showMoreButtonIfNeeded(); //Episodes check for previous and then call this above
if (mBaseItem.getBaseItemType() != BaseItemType.Episode) showMoreButtonIfNeeded(); //Episodes check for previous and then call this above
}
@@ -1361,7 +1393,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
mWatchedToggleButton.setImageResource(R.drawable.redcheck);
mWatchedToggleButton.setImageResource(R.drawable.ic_watch_red);
//adjust resume
if (mResumeButton != null && !mBaseItem.getCanResume())
mResumeButton.setVisibility(View.GONE);
@@ -1378,7 +1410,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
mWatchedToggleButton.setImageResource(R.drawable.whitecheck);
mWatchedToggleButton.setImageResource(R.drawable.ic_watch);
//adjust resume
if (mResumeButton != null && !mBaseItem.getCanResume())
mResumeButton.setVisibility(View.GONE);
@@ -1392,13 +1424,13 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
protected void play(final BaseItemDto item, final int pos, final boolean shuffle) {
final Activity activity = this;
PlaybackHelper.getItemsToPlay(item, pos == 0 && item.getType().equals("Movie"), shuffle, new Response<List<BaseItemDto>>() {
PlaybackHelper.getItemsToPlay(item, pos == 0 && item.getBaseItemType() == BaseItemType.Movie, shuffle, new Response<List<BaseItemDto>>() {
@Override
public void onResponse(List<BaseItemDto> response) {
if ("MusicArtist".equals(item.getType())) {
if (item.getBaseItemType() == BaseItemType.MusicArtist) {
MediaManager.playNow(response);
} else {
Intent intent = new Intent(activity, mApplication.getPlaybackActivityClass(item.getType()));
Intent intent = new Intent(activity, mApplication.getPlaybackActivityClass(item.getBaseItemType()));
MediaManager.setCurrentVideoQueue(response);
intent.putExtra("Position", pos);
startActivity(intent);
@@ -1410,7 +1442,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
protected void play(final BaseItemDto[] items, final int pos, final boolean shuffle) {
List<BaseItemDto> itemsToPlay = Arrays.asList(items);
Intent intent = new Intent(this, mApplication.getPlaybackActivityClass(items[0].getType()));
Intent intent = new Intent(this, mApplication.getPlaybackActivityClass(items[0].getBaseItemType()));
if (shuffle) Collections.shuffle(itemsToPlay);
MediaManager.setCurrentVideoQueue(itemsToPlay);
intent.putExtra("Position", pos);

View File

@@ -7,7 +7,7 @@ import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import androidx.leanback.app.BackgroundManager;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -57,6 +57,7 @@ import java.util.List;
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.BaseItemType;
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
import org.jellyfin.apiclient.model.library.PlayAccess;
import org.jellyfin.apiclient.model.playlists.PlaylistItemQuery;
@@ -153,7 +154,7 @@ public class ItemListActivity extends BaseActivity {
mItemList.setRowClickedListener(new ItemRowView.RowClickedListener() {
@Override
public void onRowClicked(ItemRowView row) {
showMenu(row, !"Audio".equals(row.getItem().getType()));
showMenu(row, row.getItem().getBaseItemType() != BaseItemType.Audio);
}
});
@@ -194,7 +195,6 @@ public class ItemListActivity extends BaseActivity {
BackgroundManager backgroundManager = BackgroundManager.getInstance(this);
backgroundManager.attach(getWindow());
backgroundManager.setDimLayer(getDrawable(R.drawable.left_fade));
mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
mItemId = getIntent().getStringExtra("ItemId");
@@ -331,7 +331,7 @@ public class ItemListActivity extends BaseActivity {
return true;
}
});
if ("Audio".equals(row.getItem().getType())) {
if (row.getItem().getBaseItemType() == BaseItemType.Audio) {
MenuItem mix = menu.getMenu().add(0, 1, order++, R.string.lbl_instant_mix);
mix.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
@@ -357,7 +357,7 @@ public class ItemListActivity extends BaseActivity {
item.setOverview(getString(R.string.desc_automatic_fav_songs));
item.setPlayAccess(PlayAccess.Full);
item.setMediaType("Audio");
item.setType("Playlist");
item.setBaseItemType(BaseItemType.Playlist);
item.setIsFolder(true);
setBaseItem(item);
break;
@@ -368,7 +368,7 @@ public class ItemListActivity extends BaseActivity {
queue.setOverview(getString(R.string.desc_current_video_queue));
queue.setPlayAccess(PlayAccess.Full);
queue.setMediaType("Video");
queue.setType("Playlist");
queue.setBaseItemType(BaseItemType.Playlist);
queue.setIsFolder(true);
if (MediaManager.getCurrentVideoQueue() != null) {
long runtime = 0;
@@ -406,7 +406,7 @@ public class ItemListActivity extends BaseActivity {
updatePoster(mBaseItem);
//get items
if ("Playlist".equals(mBaseItem.getType())) {
if (mBaseItem.getBaseItemType() == BaseItemType.Playlist) {
// Have to use different query here
switch (mItemId) {
case FAV_SONGS:
@@ -486,10 +486,10 @@ public class ItemListActivity extends BaseActivity {
private void updatePoster(BaseItemDto item){
switch (mItemId) {
case FAV_SONGS:
mPoster.setImageResource(R.drawable.genericmusic);
mPoster.setImageResource(R.drawable.favorites);
break;
case VIDEO_QUEUE:
mPoster.setImageResource(R.drawable.transplaylist);
mPoster.setImageResource(R.drawable.ic_video_queue);
break;
default:
// Figure image size
@@ -516,7 +516,7 @@ public class ItemListActivity extends BaseActivity {
for (String genre : mBaseItem.getGenres()) {
if (!first) InfoLayoutHelper.addSpacer(this, layout, " / ", 14);
first = false;
layout.addView(new GenreButton(this, roboto, 16, genre, mBaseItem.getType()));
layout.addView(new GenreButton(this, roboto, 16, genre, mBaseItem.getBaseItemType()));
}
}
}
@@ -541,7 +541,7 @@ public class ItemListActivity extends BaseActivity {
private void play(List<BaseItemDto> items) {
if ("Video".equals(mBaseItem.getMediaType())) {
Intent intent = new Intent(mActivity, mApplication.getPlaybackActivityClass(mBaseItem.getType()));
Intent intent = new Intent(mActivity, mApplication.getPlaybackActivityClass(mBaseItem.getBaseItemType()));
//Resume first item if needed
BaseItemDto first = items.size() > 0 ? items.get(0) : null;
if (first != null && first.getUserData() != null) {
@@ -560,7 +560,7 @@ public class ItemListActivity extends BaseActivity {
private void addButtons(int buttonSize) {
if (BaseItemUtils.canPlay(mBaseItem)) {
TextUnderButton play = new TextUnderButton(this, R.drawable.play, buttonSize, 2, getString(mBaseItem.getIsFolderItem() ? R.string.lbl_play_all : R.string.lbl_play), new View.OnClickListener() {
TextUnderButton play = new TextUnderButton(this, R.drawable.ic_play, buttonSize, 2, getString(mBaseItem.getIsFolderItem() ? R.string.lbl_play_all : R.string.lbl_play), new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mItems.size() > 0) {
@@ -574,11 +574,11 @@ public class ItemListActivity extends BaseActivity {
mButtonRow.addView(play);
play.requestFocus();
if (mBaseItem.getIsFolderItem()) {
TextUnderButton shuffle = new TextUnderButton(this, R.drawable.shuffle, buttonSize, 2, getString(R.string.lbl_shuffle_all), new View.OnClickListener() {
TextUnderButton shuffle = new TextUnderButton(this, R.drawable.ic_shuffle, buttonSize, 2, getString(R.string.lbl_shuffle_all), new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mItems.size() > 0) {
if (mBaseItem.getId().equals(VIDEO_QUEUE) || mBaseItem.getId().equals(FAV_SONGS)) {
if (mBaseItem.getId().equals(VIDEO_QUEUE) || mBaseItem.getId().equals(FAV_SONGS) || "Playlist".equals(mBaseItem.getType())) {
List<BaseItemDto> shuffled = new ArrayList<>(mItems);
Collections.shuffle(shuffled);
play(shuffled);
@@ -597,8 +597,8 @@ public class ItemListActivity extends BaseActivity {
}
}
if ("MusicAlbum".equals(mBaseItem.getType())) {
TextUnderButton mix = new TextUnderButton(this, R.drawable.mix, buttonSize, 2, getString(R.string.lbl_instant_mix), new View.OnClickListener() {
if (mBaseItem.getBaseItemType() == BaseItemType.MusicAlbum) {
TextUnderButton mix = new TextUnderButton(this, R.drawable.ic_mix, buttonSize, 2, getString(R.string.lbl_instant_mix), new View.OnClickListener() {
@Override
public void onClick(final View v) {
Utils.beep();
@@ -612,7 +612,7 @@ public class ItemListActivity extends BaseActivity {
if (!mItemId.equals(FAV_SONGS)) {
if (!mItemId.equals(VIDEO_QUEUE)) {
//Favorite
TextUnderButton fav = new TextUnderButton(this, mBaseItem.getUserData().getIsFavorite() ? R.drawable.redheart : R.drawable.whiteheart, buttonSize,2, getString(R.string.lbl_toggle_favorite), new View.OnClickListener() {
TextUnderButton fav = new TextUnderButton(this, mBaseItem.getUserData().getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart, buttonSize,2, getString(R.string.lbl_favorite), new View.OnClickListener() {
@Override
public void onClick(final View v) {
UserItemDataDto data = mBaseItem.getUserData();
@@ -620,7 +620,7 @@ public class ItemListActivity extends BaseActivity {
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
((ImageButton) v).setImageResource(response.getIsFavorite() ? R.drawable.redheart : R.drawable.whiteheart);
((ImageButton) v).setImageResource(response.getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart);
TvApp.getApplication().setLastFavoriteUpdate(System.currentTimeMillis());
}
});
@@ -631,9 +631,9 @@ public class ItemListActivity extends BaseActivity {
}
if ("Playlist".equals(mBaseItem.getType())) {
if (mBaseItem.getBaseItemType() == BaseItemType.Playlist) {
if (VIDEO_QUEUE.equals(mBaseItem.getId())) {
mButtonRow.addView(new TextUnderButton(this, R.drawable.saveplaylist, buttonSize, 2, getString(R.string.lbl_save_as_playlist), new View.OnClickListener() {
mButtonRow.addView(new TextUnderButton(this, R.drawable.ic_save, buttonSize, 2, getString(R.string.lbl_save_as_playlist), new View.OnClickListener() {
@Override
public void onClick(View v) {
MediaManager.saveVideoQueue(mActivity);
@@ -641,7 +641,7 @@ public class ItemListActivity extends BaseActivity {
}));
}
TextUnderButton delete = new TextUnderButton(this, R.drawable.trash, buttonSize, getString(R.string.lbl_delete), new View.OnClickListener() {
TextUnderButton delete = new TextUnderButton(this, R.drawable.ic_trash, buttonSize, getString(R.string.lbl_delete), new View.OnClickListener() {
@Override
public void onClick(final View v) {
if (mBaseItem.getId().equals(VIDEO_QUEUE)) {
@@ -702,7 +702,7 @@ public class ItemListActivity extends BaseActivity {
}
if (mBaseItem.getAlbumArtists() != null && mBaseItem.getAlbumArtists().size() > 0) {
TextUnderButton artist = new TextUnderButton(this, R.drawable.user, buttonSize, 4, getString(R.string.lbl_open_artist), new View.OnClickListener() {
TextUnderButton artist = new TextUnderButton(this, R.drawable.ic_user, buttonSize, 4, getString(R.string.lbl_open_artist), new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent artist = new Intent(mActivity, FullDetailsActivity.class);

View File

@@ -4,7 +4,7 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.Row;
import androidx.leanback.widget.Row;
import android.view.View;
import org.jellyfin.androidtv.model.InfoItem;

View File

@@ -3,15 +3,15 @@ package org.jellyfin.androidtv.details;
import android.animation.Animator;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.view.KeyEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -56,7 +56,7 @@ public class PhotoPlayerActivity extends BaseActivity {
boolean isPlaying;
FrameLayout mPopupArea;
RowsFragment mPopupRowsFragment;
RowsSupportFragment mPopupRowsFragment;
ArrayObjectAdapter mPopupRowAdapter;
ListRow mThumbRow;
PositionableListRowPresenter mPopupRowPresenter;
@@ -95,13 +95,13 @@ public class PhotoPlayerActivity extends BaseActivity {
loadNext();
loadPrev();
// Inject the RowsFragment in the popup container
// Inject the RowsSupportFragment in the popup container
if (getFragmentManager().findFragmentById(R.id.rows_area) == null) {
mPopupRowsFragment = new RowsFragment();
getFragmentManager().beginTransaction()
mPopupRowsFragment = new RowsSupportFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.rows_area, mPopupRowsFragment).commit();
} else {
mPopupRowsFragment = (RowsFragment) getFragmentManager()
mPopupRowsFragment = (RowsSupportFragment) getSupportFragmentManager()
.findFragmentById(R.id.rows_area);
}
@@ -344,7 +344,7 @@ public class PhotoPlayerActivity extends BaseActivity {
.resize(displayWidth, displayHeight)
.centerInside()
.skipMemoryCache()
.error(R.drawable.photo)
.error(R.drawable.tile_land_photo)
.into(target, new Callback() {
@Override
public void onSuccess() {

View File

@@ -154,7 +154,7 @@ public class TvApiEventListener extends ApiEventListener {
switch (response.getItems()[0].getMediaType()) {
case "Video":
MediaManager.setCurrentVideoQueue(Arrays.asList(response.getItems()));
Intent intent = new Intent(TvApp.getApplication().getCurrentActivity(), TvApp.getApplication().getPlaybackActivityClass(response.getItems()[0].getType()));
Intent intent = new Intent(TvApp.getApplication().getCurrentActivity(), TvApp.getApplication().getPlaybackActivityClass(response.getItems()[0].getBaseItemType()));
TvApp.getApplication().getCurrentActivity().startActivity(intent);
break;
case "Audio":

View File

@@ -2,7 +2,7 @@ package org.jellyfin.androidtv.imagehandling;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.app.BackgroundManager;
import androidx.leanback.app.BackgroundManager;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;

View File

@@ -1,51 +0,0 @@
package org.jellyfin.androidtv.integration;
public class Recommendation {
private RecommendationType type;
private String itemId;
private Integer recId;
private long dateAdded;
public Recommendation(RecommendationType type, String id) {
itemId = id;
this.type = type;
this.dateAdded = System.currentTimeMillis();
}
public String getItemId() {
return itemId;
}
public void setItemId(String mItemId) {
this.itemId = mItemId;
}
public Integer getRecId() {
return recId;
}
public void setRecId(Integer mRecId) {
this.recId = mRecId;
}
public long getDateAdded() {
return dateAdded;
}
public RecommendationType getType() {
return type;
}
public void setType(RecommendationType mType) {
this.type = mType;
}
@Override
public boolean equals(Object o) {
if (o instanceof Recommendation) {
Recommendation compare = (Recommendation)o;
return compare.getType() == this.getType() && compare.getItemId().equals(this.getItemId());
}
return false;
}
}

View File

@@ -1,126 +0,0 @@
package org.jellyfin.androidtv.integration;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.jellyfin.androidtv.util.Utils;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* Created by Eric on 3/1/2015.
*/
public class RecommendationBuilder {
private static final String TAG = "RecommendationBuilder";
private Context mContext;
private int mId;
private int mPriority;
private int mSmallIcon;
private String mTitle;
private String mDescription;
private Bitmap mBitmap;
private String mBackgroundUri;
private PendingIntent mIntent;
public RecommendationBuilder() {
}
public RecommendationBuilder setContext(Context context) {
mContext = context;
return this;
}
public RecommendationBuilder setId(int id) {
mId = id;
return this;
}
public RecommendationBuilder setPriority(int priority) {
mPriority = priority;
return this;
}
public RecommendationBuilder setTitle(String title) {
mTitle = title;
return this;
}
public RecommendationBuilder setDescription(String description) {
mDescription = description;
return this;
}
public RecommendationBuilder setBitmap(Bitmap bitmap) {
mBitmap = bitmap;
return this;
}
public RecommendationBuilder setBackground(String uri) {
mBackgroundUri = uri;
return this;
}
public RecommendationBuilder setIntent(PendingIntent intent) {
mIntent = intent;
return this;
}
public RecommendationBuilder setSmallIcon(int resourceId) {
mSmallIcon = resourceId;
return this;
}
public Notification build() {
Log.d(TAG, "Building notification - " + this.toString());
Bundle extras = new Bundle();
if (mBackgroundUri != null) {
try {
Log.d(TAG, "Background - " + Uri.parse(RecommendationContentProvider.CONTENT_URI + URLEncoder.encode(mBackgroundUri, "UTF-8") + "/0").toString());
extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, Uri.parse(RecommendationContentProvider.CONTENT_URI + URLEncoder.encode(mBackgroundUri, "UTF-8")).toString());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
Notification notification = new NotificationCompat.BigPictureStyle(
new NotificationCompat.Builder(mContext)
.setContentTitle(mTitle)
.setContentText(mDescription)
.setPriority(mPriority)
.setLocalOnly(true)
.setOngoing(true)
.setColor(Utils.getBrandColor())
.setCategory(Notification.CATEGORY_RECOMMENDATION)
.setLargeIcon(mBitmap)
.setSmallIcon(mSmallIcon)
.setContentIntent(mIntent)
.setExtras(extras))
.build();
return notification;
}
@Override
public String toString() {
return "RecommendationBuilder{" +
", mId=" + mId +
", mPriority=" + mPriority +
", mSmallIcon=" + mSmallIcon +
", mTitle='" + mTitle + '\'' +
", mDescription='" + mDescription + '\'' +
", mBitmap='" + mBitmap + '\'' +
", mBackgroundUri='" + mBackgroundUri + '\'' +
", mIntent=" + mIntent +
'}';
}}

View File

@@ -1,157 +0,0 @@
package org.jellyfin.androidtv.integration;
/**
* The MIT License (MIT)
* Copyright (c) 2014 David Carver
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkUrlFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
/**
* Provides a background image for Recommendations for a RecommendationCardView.
*
* The card view changed to require a content provider be created. It requests
* items via the openFile(Uri uri) method. This is an example of how to retrieve
* an image from a web site and provide a ParcelFileDescriptor back that
* contains the contents.
*
* This is based on code from the following stackoverflow description on how to
* populate a ParcelFileDescriptor from any input stream.
*
* http://stackoverflow.com/a/14734310/1950264
*
* You still need to setup a ContentProvider entry and Authority in the
* AndroidManifest.xml
*
* See
* http://developer.android.com/reference/android/content/ContentProvider.html
*
*/
public class RecommendationContentProvider extends ContentProvider {
public static String AUTHORITY = "org.jellyfin.androidtv.recommendations";
public static String CONTENT_URI = "content://" + AUTHORITY + "/";
@Override
public boolean onCreate() {
return true;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
ParcelFileDescriptor[] pipe = null;
String url = uri.getPath();
try {
String decodedUrl = URLDecoder.decode(url.replaceFirst("/", ""),
"UTF-8");
pipe = ParcelFileDescriptor.createPipe();
// This uses OKHttp to
OkHttpClient httpClient = new OkHttpClient();
HttpURLConnection connection = new OkUrlFactory(httpClient).open(new URL(decodedUrl));
new TransferThread(connection.getInputStream(),
new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]))
.start();
} catch (IOException e) {
Log.e(getClass().getSimpleName(), "Exception opening pipe", e);
throw new FileNotFoundException("Could not open pipe for: "
+ uri.toString());
}
return (pipe[0]);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return "image/*";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
static class TransferThread extends Thread {
InputStream in;
OutputStream out;
TransferThread(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
}
@Override
public void run() {
byte[] buf = new byte[8192];
int len;
try {
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.flush();
out.close();
} catch (IOException e) {
Log.e(getClass().getSimpleName(),
"Exception transferring file", e);
}
}
}
}

View File

@@ -1,332 +0,0 @@
package org.jellyfin.androidtv.integration;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.browsing.MainActivity;
import org.jellyfin.androidtv.querying.StdItemQuery;
import org.jellyfin.androidtv.startup.StartupActivity;
import org.jellyfin.androidtv.util.ImageUtils;
import org.jellyfin.androidtv.util.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.entities.LocationType;
import org.jellyfin.apiclient.model.entities.SortOrder;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemFilter;
import org.jellyfin.apiclient.model.querying.ItemSortBy;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import org.jellyfin.apiclient.model.querying.NextUpQuery;
import org.jellyfin.apiclient.model.querying.SimilarItemsQuery;
/**
* Created by Eric on 3/1/2015.
*/
public class RecommendationManager {
private final String REC_FILE_NAME = "reccomendations.json";
private final Integer MAX_TV_RECS = 3;
private final Integer MAX_MOVIE_RECS = 4;
private boolean isEnabled;
private static RecommendationManager instance;
private Recommendations mRecommendations;
public RecommendationManager() {
isEnabled = Build.VERSION.SDK_INT >= 21;
mRecommendations = loadRecs();
if (isEnabled) {
validate();
} else {
TvApp.getApplication().getLogger().Info("Recommendations not enabled on this device");
}
}
public static RecommendationManager getInstance() {
if (instance == null) instance = new RecommendationManager();
return instance;
}
public static void init() {
if (instance == null) {
getInstance();
} else {
instance.validate();
}
}
private Recommendations loadRecs() {
if (isEnabled) {
try {
InputStream recFile = TvApp.getApplication().openFileInput(REC_FILE_NAME);
String json = Utils.readStringFromStream(recFile);
recFile.close();
return (Recommendations) TvApp.getApplication().getSerializer().DeserializeFromString(json, Recommendations.class);
} catch (IOException e) {
// none saved
return new Recommendations(TvApp.getApplication().getApiClient().getServerInfo().getId(), TvApp.getApplication().getCurrentUser().getId());
}
} else {
return new Recommendations(TvApp.getApplication().getApiClient().getServerInfo().getId(), TvApp.getApplication().getCurrentUser().getId());
}
}
public boolean validate() {
if (isEnabled) {
//Now validate that these are for this server and user.
if (mRecommendations == null || mRecommendations.getServerId() == null || TvApp.getApplication().getApiClient().getServerInfo() == null || !mRecommendations.getServerId().equals(TvApp.getApplication().getApiClient().getServerInfo().getId())
|| mRecommendations.getUserId() == null || TvApp.getApplication().getCurrentUser() == null || !mRecommendations.getUserId().equals(TvApp.getApplication().getCurrentUser().getId())) {
//Nope - clear them out and start over for this user
clearAll();
createAll();
TvApp.getApplication().getLogger().Info("Recommendations re-set for user "+TvApp.getApplication().getCurrentUser().getName());
return false;
}
createAll();
TvApp.getApplication().getLogger().Info("Recommendations re-created for user "+TvApp.getApplication().getCurrentUser().getName());
}
return true;
}
private void saveRecs() {
if (isEnabled) {
try {
OutputStream recFile = TvApp.getApplication().openFileOutput(REC_FILE_NAME, Context.MODE_PRIVATE);
recFile.write(TvApp.getApplication().getSerializer().SerializeToString(mRecommendations).getBytes());
recFile.close();
} catch (IOException e) {
TvApp.getApplication().getLogger().ErrorException("Error saving recommendations",e);
}
}
}
public void clearAll() {
NotificationManager nm = (NotificationManager)
TvApp.getApplication().getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancelAll();
mRecommendations = new Recommendations(TvApp.getApplication().getApiClient().getServerInfo().getId(), TvApp.getApplication().getCurrentUser().getId());
saveRecs();
}
public void createAll() {
if (isEnabled) {
//Create recs for next up tv and movies
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
nextUpQuery.setLimit(MAX_TV_RECS);
nextUpQuery.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio});
TvApp.getApplication().getApiClient().GetNextUpEpisodesAsync(nextUpQuery, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getTotalRecordCount() > 0) {
for (BaseItemDto episode : response.getItems()) {
if (episode.getLocationType() != LocationType.Virtual) addRecommendation(episode, RecommendationType.Tv);
}
}
}
});
//First try for resumables
StdItemQuery resumeMovies = new StdItemQuery();
resumeMovies.setIncludeItemTypes(new String[]{"Movie"});
resumeMovies.setRecursive(true);
resumeMovies.setLimit(2);
resumeMovies.setFilters(new ItemFilter[]{ItemFilter.IsResumable});
resumeMovies.setSortBy(new String[]{ItemSortBy.DatePlayed});
resumeMovies.setSortOrder(SortOrder.Descending);
TvApp.getApplication().getApiClient().GetItemsAsync(resumeMovies, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
int movieItems = 0;
if (response.getTotalRecordCount() > 0) {
for (BaseItemDto movie : response.getItems()) {
recommend(movie.getId());
movieItems++;
}
}
if (movieItems < MAX_MOVIE_RECS) {
//Now fill in with latest movies
StdItemQuery suggMovies = new StdItemQuery();
suggMovies.setIncludeItemTypes(new String[]{"Movie"});
suggMovies.setRecursive(true);
suggMovies.setLimit(MAX_MOVIE_RECS - movieItems);
suggMovies.setFilters(new ItemFilter[]{ItemFilter.IsUnplayed});
suggMovies.setSortBy(new String[]{ItemSortBy.DateCreated});
suggMovies.setSortOrder(SortOrder.Descending);
TvApp.getApplication().getApiClient().GetItemsAsync(suggMovies, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult suggResponse) {
if (suggResponse.getTotalRecordCount() > 0) {
for (BaseItemDto movie : suggResponse.getItems()) {
addRecommendation(movie, RecommendationType.Movie);
}
}
}
});
}
}
});
}
}
public void recommend(final String itemId) {
if (isEnabled) {
if (itemId == null) {
TvApp.getApplication().getLogger().Error("Attempt to recommend null Item");
return;
}
//No matter what it is, if it is resumable, recommend this item (need to re-retrieve for current user data)
TvApp.getApplication().getApiClient().GetItemAsync(itemId, TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
if (response == null) {
TvApp.getApplication().getLogger().Error("No item found with ID: "+itemId);
return;
}
if (response.getCanResume()) {
addRecommendation(response, RecommendationType.Movie);
} else {
switch (response.getType()) {
case "Movie":
//First remove us if we were a recommendation
mRecommendations.remove(RecommendationType.Movie, response.getId());
//Suggest a similar movie
SimilarItemsQuery similar = new SimilarItemsQuery();
similar.setId(response.getId());
similar.setLimit(1);
similar.setUserId(TvApp.getApplication().getCurrentUser().getId());
TvApp.getApplication().getApiClient().GetSimilarItems(similar, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult similarResponse) {
if (similarResponse.getTotalRecordCount() > 0) {
addRecommendation(similarResponse.getItems()[0], RecommendationType.Movie);
}
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().ErrorException("Error retrieving item for recommendation", exception);
}
});
break;
case "Episode":
//First remove us if we were a recommendation
mRecommendations.remove(RecommendationType.Tv, response.getId());
//Suggest the next up episode
NextUpQuery next = new NextUpQuery();
next.setSeriesId(response.getSeriesId());
next.setUserId(TvApp.getApplication().getCurrentUser().getId());
next.setLimit(1);
TvApp.getApplication().getApiClient().GetNextUpEpisodesAsync(next, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult nextResponse) {
if (nextResponse.getTotalRecordCount() > 0 && nextResponse.getItems()[0].getLocationType() != LocationType.Virtual) {
addRecommendation(nextResponse.getItems()[0], RecommendationType.Tv);
}
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().ErrorException("Error retrieving item for recommendation", exception);
}
});
break;
}
}
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().ErrorException("Error retrieving item for recommendation", exception);
}
});
}
}
public boolean addRecommendation(BaseItemDto item, RecommendationType type) {
if (isEnabled) {
//No need if we are already there
if (mRecommendations.get(type, item.getId()) != null) return false;
//Not so build one
new AsyncRunner().execute(item, type);
return true;
} else {
return false;
}
}
private class AsyncRunner extends AsyncTask<Object, Integer, Boolean> {
@Override
protected Boolean doInBackground(Object... params) {
BaseItemDto item = (BaseItemDto) params[0];
RecommendationType type = (RecommendationType) params[1];
Recommendation rec = new Recommendation(type, item.getId());
rec.setRecId(mRecommendations.getRecId(type, type == RecommendationType.Movie ? MAX_MOVIE_RECS : MAX_TV_RECS));
RecommendationBuilder builder = new RecommendationBuilder()
.setContext(TvApp.getApplication())
.setSmallIcon(R.drawable.logoicon114);
Notification recommendation = builder
.setId(rec.getRecId())
.setPriority(0)
.setTitle(item.getName())
.setDescription(item.getOverview())
.setBitmap(Utils.getBitmapFromURL(ImageUtils.getPrimaryImageUrl(item, TvApp.getApplication().getApiClient(), true, true, false, 300)))
.setBackground(ImageUtils.getBackdropImageUrl(item, TvApp.getApplication().getApiClient(), true))
.setIntent(buildPendingIntent(item))
.build();
mRecommendations.add(rec);
saveRecs();
NotificationManager notificationManager = (NotificationManager)
TvApp.getApplication().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(rec.getRecId(), recommendation);
return true;
}
}
private PendingIntent buildPendingIntent(BaseItemDto item) {
Intent directIntent = new Intent(TvApp.getApplication(), StartupActivity.class);
directIntent.putExtra("ItemId", item.getId());
directIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(TvApp.getApplication());
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(directIntent);
// Ensure a unique PendingIntents, otherwise all recommendations end up with the same
// PendingIntent
directIntent.setAction(item.getId());
return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
}

View File

@@ -1,6 +0,0 @@
package org.jellyfin.androidtv.integration;
public enum RecommendationType {
Movie,
Tv
}

View File

@@ -1,129 +0,0 @@
package org.jellyfin.androidtv.integration;
import android.app.NotificationManager;
import android.content.Context;
import org.jellyfin.androidtv.TvApp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by Eric on 3/1/2015.
*/
public class Recommendations {
private String serverId;
private String userId;
private List<Recommendation> tvRecommendations = new ArrayList<>();
private List<Recommendation> movieRecommendations = new ArrayList<>();
public Recommendations(String serverId, String userId) {
this.userId = userId;
this.serverId = serverId;
}
public List<Recommendation> getTvRecommendations() {
return tvRecommendations;
}
public void setTvRecommendations(List<Recommendation> tvRecommendations) {
this.tvRecommendations = tvRecommendations;
}
public List<Recommendation> getMovieRecommendations() {
return movieRecommendations;
}
public void setMovieRecommendations(List<Recommendation> movieRecommendations) {
this.movieRecommendations = movieRecommendations;
}
public Recommendation get(RecommendationType type, String id) {
Recommendation compare = new Recommendation(type, id);
switch (type) {
case Movie:
return movieRecommendations.contains(compare) ? movieRecommendations.get(movieRecommendations.indexOf(compare)) : null;
case Tv:
return tvRecommendations.contains(compare) ? tvRecommendations.get(tvRecommendations.indexOf(compare)) : null;
}
return null;
}
public boolean add(Recommendation rec) {
switch (rec.getType()) {
case Movie:
movieRecommendations.add(rec);
break;
case Tv:
tvRecommendations.add(rec);
break;
}
return true;
}
public Integer getRecId(RecommendationType type, int max) {
switch (type) {
case Movie:
if (movieRecommendations.size() < max) return 100 + movieRecommendations.size()+1;
return replaceOldest(type);
case Tv:
if (tvRecommendations.size() < max) return 200 + tvRecommendations.size()+1;
return replaceOldest(type);
}
throw new IllegalArgumentException("type");
}
private Integer replaceOldest(RecommendationType type) {
List<Recommendation> list = type == RecommendationType.Movie ? movieRecommendations : tvRecommendations;
Recommendation oldest = Collections.min(list, new Comparator<Recommendation>() {
@Override
public int compare(Recommendation lhs, Recommendation rhs) {
return Long.compare(lhs.getDateAdded(), rhs.getDateAdded());
}
});
list.remove(oldest);
((NotificationManager) TvApp.getApplication().getSystemService(Context.NOTIFICATION_SERVICE)).cancel(oldest.getRecId());
return oldest.getRecId();
}
public boolean remove(RecommendationType type, String id) {
List<Recommendation> list = type == RecommendationType.Movie ? movieRecommendations : tvRecommendations;
Recommendation existing = get(type, id);
if (existing != null) {
list.remove(existing);
((NotificationManager) TvApp.getApplication().getSystemService(Context.NOTIFICATION_SERVICE)).cancel(existing.getRecId());
return true;
}
return false;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
}

View File

@@ -13,6 +13,7 @@ import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.BaseItemUtils;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import org.jellyfin.apiclient.interaction.EmptyResponse;
@@ -20,6 +21,7 @@ import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.apiclient.ServerInfo;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemPerson;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.entities.ImageType;
import org.jellyfin.apiclient.model.livetv.ChannelInfoDto;
@@ -43,7 +45,6 @@ public class BaseRowItem {
private SelectAction selectAction = SelectAction.ShowDetails;
private boolean isPlaying;
public BaseRowItem(int index, BaseItemDto item) {
this(index, item, false, false);
}
@@ -51,10 +52,11 @@ public class BaseRowItem {
public BaseRowItem(int index, BaseItemDto item, boolean preferParentThumb, boolean staticHeight) {
this(index, item, preferParentThumb, staticHeight, SelectAction.ShowDetails);
}
public BaseRowItem(int index, BaseItemDto item, boolean preferParentThumb, boolean staticHeight, SelectAction selectAction) {
this.index = index;
this.baseItem = item;
type = item.getType().equals("Program") ? ItemType.LiveTvProgram : item.getType().equals("Recording") ? ItemType.LiveTvRecording : ItemType.BaseItem;
this.type = item.getBaseItemType() == BaseItemType.Program ? ItemType.LiveTvProgram : item.getBaseItemType() == BaseItemType.Recording ? ItemType.LiveTvRecording : ItemType.BaseItem;
this.preferParentThumb = preferParentThumb;
this.staticHeight = staticHeight;
this.selectAction = selectAction;
@@ -63,10 +65,12 @@ public class BaseRowItem {
public BaseRowItem(int index, ChannelInfoDto channel) {
this.index = index;
this.channelInfo = channel;
type = ItemType.LiveTvChannel;
this.type = ItemType.LiveTvChannel;
}
public BaseRowItem(BaseItemDto program, boolean staticHeight) { this(0, program, false, staticHeight); }
public BaseRowItem(BaseItemDto program, boolean staticHeight) {
this(0, program, false, staticHeight);
}
public BaseRowItem(BaseItemDto program) {
this(0, program);
@@ -85,12 +89,12 @@ public class BaseRowItem {
public BaseRowItem(BaseItemPerson person) {
this.person = person;
this.staticHeight = true;
type = ItemType.Person;
this.type = ItemType.Person;
}
public BaseRowItem(UserDto user) {
this.user = user;
type = ItemType.User;
this.type = ItemType.User;
}
public BaseRowItem(SearchHint hint) {
@@ -101,47 +105,97 @@ public class BaseRowItem {
public BaseRowItem(ChapterItemInfo chapter) {
this.chapterInfo = chapter;
this.staticHeight = true;
type = ItemType.Chapter;
this.type = ItemType.Chapter;
}
public BaseRowItem(GridButton button) {
this.gridButton = button;
type = ItemType.GridButton;
staticHeight = true;
this.type = ItemType.GridButton;
this.staticHeight = true;
}
public int getIndex() {
return index;
}
public void setIndex(int ndx) { index = ndx; }
public void setIndex(int ndx) {
index = ndx;
}
public BaseItemDto getBaseItem() {
return baseItem;
}
public BaseItemPerson getPerson() { return person; }
public ChapterItemInfo getChapterInfo() { return chapterInfo; }
public ServerInfo getServerInfo() { return serverInfo; }
public UserDto getUser() { return user; }
public SearchHint getSearchHint() { return searchHint; }
public ChannelInfoDto getChannelInfo() { return channelInfo; }
public BaseItemDto getProgramInfo() { return baseItem; }
public BaseItemDto getRecordingInfo() { return baseItem; }
public SeriesTimerInfoDto getSeriesTimerInfo() { return seriesTimerInfo; }
public GridButton getGridButton() { return gridButton; }
public boolean isChapter() { return type == ItemType.Chapter; }
public boolean isPerson() { return type == ItemType.Person; }
public boolean isBaseItem() { return type == ItemType.BaseItem; }
public boolean getPreferParentThumb() { return preferParentThumb; }
public ItemType getItemType() { return type; }
public BaseItemPerson getPerson() {
return person;
}
public ChapterItemInfo getChapterInfo() {
return chapterInfo;
}
public ServerInfo getServerInfo() {
return serverInfo;
}
public UserDto getUser() {
return user;
}
public SearchHint getSearchHint() {
return searchHint;
}
public ChannelInfoDto getChannelInfo() {
return channelInfo;
}
public BaseItemDto getProgramInfo() {
return baseItem;
}
public BaseItemDto getRecordingInfo() {
return baseItem;
}
public SeriesTimerInfoDto getSeriesTimerInfo() {
return seriesTimerInfo;
}
public GridButton getGridButton() {
return gridButton;
}
public boolean isChapter() {
return type == ItemType.Chapter;
}
public boolean isPerson() {
return type == ItemType.Person;
}
public boolean isBaseItem() {
return type == ItemType.BaseItem;
}
public boolean getPreferParentThumb() {
return preferParentThumb;
}
public ItemType getItemType() {
return type;
}
public boolean isFolder() {
return type == ItemType.BaseItem && baseItem != null && baseItem.getIsFolderItem();
}
public boolean showCardInfoOverlay() {return type == ItemType.BaseItem && baseItem != null
&& ("Folder".equals(baseItem.getType()) || "PhotoAlbum".equals(baseItem.getType()) || "RecordingGroup".equals(baseItem.getType())
|| "UserView".equals(baseItem.getType()) || "CollectionFolder".equals(baseItem.getType()) || "Photo".equals(baseItem.getType())
|| "Video".equals(baseItem.getType()) || "Person".equals(baseItem.getType()) || "Playlist".equals(baseItem.getType())
|| "MusicArtist".equals(baseItem.getType()));
public boolean showCardInfoOverlay() {
return type == ItemType.BaseItem && baseItem != null
&& Arrays.asList(BaseItemType.Folder, BaseItemType.PhotoAlbum, BaseItemType.RecordingGroup,
BaseItemType.UserView, BaseItemType.CollectionFolder, BaseItemType.Photo,
BaseItemType.Video, BaseItemType.Person, BaseItemType.Playlist,
BaseItemType.MusicArtist).contains(baseItem.getBaseItemType());
}
public boolean isValid() {
@@ -155,7 +209,8 @@ public class BaseRowItem {
case SeriesTimer:
return seriesTimerInfo != null;
default:
return true; //compatibility
// compatibility
return true;
}
}
@@ -172,14 +227,13 @@ public class BaseRowItem {
default:
return getPrimaryImageUrl(maxHeight);
}
default:
return getPrimaryImageUrl(maxHeight);
default:
return getPrimaryImageUrl(maxHeight);
}
}
public String getPrimaryImageUrl(int maxHeight) {
switch (type) {
case BaseItem:
case LiveTvProgram:
case LiveTvRecording:
@@ -193,37 +247,33 @@ public class BaseRowItem {
case LiveTvChannel:
return ImageUtils.getPrimaryImageUrl(channelInfo, TvApp.getApplication().getApiClient());
case Server:
return "android.resource://org.jellyfin.androidtv/" + R.drawable.server;
return ImageUtils.getResourceUrl(R.drawable.tile_port_server);
case GridButton:
return "android.resource://org.jellyfin.androidtv/" + gridButton.getImageIndex();
return ImageUtils.getResourceUrl(gridButton.getImageIndex());
case SeriesTimer:
return "android.resource://org.jellyfin.androidtv/" + R.drawable.seriestimer;
return ImageUtils.getResourceUrl(R.drawable.tile_land_series_timer);
case SearchHint:
return Utils.isNonEmpty(searchHint.getPrimaryImageTag()) ? ImageUtils.getImageUrl(searchHint.getItemId(), ImageType.Primary, searchHint.getPrimaryImageTag(), TvApp.getApplication().getApiClient()) :
Utils.isNonEmpty(searchHint.getThumbImageItemId()) ? ImageUtils.getImageUrl(searchHint.getThumbImageItemId(), ImageType.Thumb, searchHint.getThumbImageTag(), TvApp.getApplication().getApiClient()) : null;
if (Utils.isNonEmpty(searchHint.getPrimaryImageTag())) {
return ImageUtils.getImageUrl(searchHint.getItemId(), ImageType.Primary, searchHint.getPrimaryImageTag(), TvApp.getApplication().getApiClient());
} else if (Utils.isNonEmpty(searchHint.getThumbImageItemId())) {
return ImageUtils.getImageUrl(searchHint.getThumbImageItemId(), ImageType.Thumb, searchHint.getThumbImageTag(), TvApp.getApplication().getApiClient());
}
}
return null;
}
public boolean isFavorite() {
switch (type) {
case BaseItem:
case LiveTvRecording:
case LiveTvProgram:
return baseItem.getUserData() != null && baseItem.getUserData().getIsFavorite();
case Person:
break;
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
break;
case LiveTvChannel:
break;
case GridButton:
break;
}
@@ -238,20 +288,13 @@ public class BaseRowItem {
case LiveTvProgram:
return baseItem.getUserData() != null && baseItem.getUserData().getPlayed();
case Person:
break;
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
break;
case LiveTvChannel:
break;
case GridButton:
break;
}
return false;
@@ -260,7 +303,9 @@ public class BaseRowItem {
public String getCardName() {
switch (type) {
case BaseItem:
if ("Audio".equals(baseItem.getType())) return baseItem.getAlbumArtist() != null ? baseItem.getAlbumArtist() : baseItem.getAlbum() != null ? baseItem.getAlbum() : "<Unknown>";
if (baseItem.getBaseItemType() == BaseItemType.Audio) {
return baseItem.getAlbumArtist() != null ? baseItem.getAlbumArtist() : baseItem.getAlbum() != null ? baseItem.getAlbum() : "<Unknown>";
}
default:
return getFullName();
}
@@ -268,7 +313,6 @@ public class BaseRowItem {
public String getFullName() {
switch (type) {
case BaseItem:
case LiveTvProgram:
case LiveTvRecording:
@@ -296,11 +340,10 @@ public class BaseRowItem {
public String getName() {
switch (type) {
case BaseItem:
case LiveTvRecording:
case LiveTvProgram:
return "Audio".equals(baseItem.getType())? getFullName() : baseItem.getName();
return baseItem.getBaseItemType() == BaseItemType.Audio ? getFullName() : baseItem.getName();
case Person:
return person.getName();
case Server:
@@ -324,7 +367,6 @@ public class BaseRowItem {
public String getItemId() {
switch (type) {
case BaseItem:
case LiveTvProgram:
case LiveTvRecording:
@@ -352,7 +394,6 @@ public class BaseRowItem {
public String getSubText() {
switch (type) {
case BaseItem:
return BaseItemUtils.getSubName(baseItem);
case Person:
@@ -361,7 +402,7 @@ public class BaseRowItem {
Long pos = chapterInfo.getStartPositionTicks() / 10000;
return TimeUtils.formatMillis(pos.intValue());
case Server:
return serverInfo.getLocalAddress() != null ? serverInfo.getLocalAddress().substring(7) : "";
return serverInfo.getAddress() != null ? serverInfo.getAddress().substring(7) : "";
case LiveTvChannel:
return channelInfo.getNumber();
case LiveTvProgram:
@@ -383,54 +424,25 @@ public class BaseRowItem {
return "";
}
public String getType() {
switch (type) {
case BaseItem:
case LiveTvRecording:
case LiveTvProgram:
return baseItem.getType();
case Person:
return person.getType();
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
return searchHint.getType();
case LiveTvChannel:
return channelInfo.getType();
case GridButton:
return "GridButton";
case SeriesTimer:
return "SeriesTimer";
}
return "";
public BaseItemType getBaseItemType() {
if (baseItem != null)
return baseItem.getBaseItemType();
else
return null;
}
public String getSummary() {
switch (type) {
case BaseItem:
case LiveTvRecording:
case LiveTvProgram:
return baseItem.getOverview();
case Person:
break;
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
break;
case LiveTvChannel:
break;
case GridButton:
break;
case SeriesTimer:
@@ -442,22 +454,15 @@ public class BaseRowItem {
public long getRuntimeTicks() {
switch (type) {
case LiveTvRecording:
case BaseItem:
return baseItem.getRunTimeTicks() != null ? baseItem.getRunTimeTicks() : 0;
case Person:
break;
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
break;
case LiveTvChannel:
break;
case GridButton:
break;
case LiveTvProgram:
@@ -469,25 +474,16 @@ public class BaseRowItem {
public int getChildCount() {
switch (type) {
case BaseItem:
return isFolder() && !"MusicArtist".equals(baseItem.getType()) && baseItem.getChildCount() != null ? baseItem.getChildCount() : -1;
return isFolder() && baseItem.getBaseItemType() != BaseItemType.MusicArtist && baseItem.getChildCount() != null ? baseItem.getChildCount() : -1;
case Person:
break;
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
break;
case LiveTvChannel:
break;
case LiveTvRecording:
break;
case GridButton:
break;
case LiveTvProgram:
break;
}
@@ -496,12 +492,11 @@ public class BaseRowItem {
}
public String getChildCountStr() {
if (baseItem != null && "Playlist".equals(baseItem.getType()) && baseItem.getCumulativeRunTimeTicks() != null) {
if (baseItem != null && baseItem.getBaseItemType() == BaseItemType.Playlist && baseItem.getCumulativeRunTimeTicks() != null) {
return TimeUtils.formatMillis(baseItem.getCumulativeRunTimeTicks() / 10000);
} else {
Integer count = getChildCount();
return count > 0 ? count.toString() : "";
}
}
@@ -509,7 +504,6 @@ public class BaseRowItem {
switch (type) {
case BaseItem:
return ImageUtils.getBackdropImageUrl(baseItem, TvApp.getApplication().getConnectionManager().GetApiClient(baseItem), true);
}
return null;
@@ -517,26 +511,24 @@ public class BaseRowItem {
public Drawable getBadgeImage() {
switch (type) {
case BaseItem:
if (baseItem.getType().equals("Movie") && baseItem.getCriticRating() != null) {
if (baseItem.getBaseItemType() == BaseItemType.Movie && baseItem.getCriticRating() != null) {
return baseItem.getCriticRating() > 59 ? TvApp.getApplication().getDrawableCompat(R.drawable.fresh) : TvApp.getApplication().getDrawableCompat(R.drawable.rotten);
} else if (baseItem.getType().equals("Program") && baseItem.getTimerId() != null) {
return baseItem.getSeriesTimerId() != null ? TvApp.getApplication().getDrawableCompat(R.drawable.recseries) : TvApp.getApplication().getDrawableCompat(R.drawable.rec);
} else if (baseItem.getBaseItemType() == BaseItemType.Program && baseItem.getTimerId() != null) {
return baseItem.getSeriesTimerId() != null ? TvApp.getApplication().getDrawableCompat(R.drawable.ic_record_series_red) : TvApp.getApplication().getDrawableCompat(R.drawable.ic_record_red);
}
break;
case Person:
break;
case Server:
break;
case User:
if (user.getHasPassword()) {
return TvApp.getApplication().getDrawableCompat(R.drawable.lock);
return TvApp.getApplication().getDrawableCompat(R.drawable.ic_lock);
}
break;
case LiveTvProgram:
if (baseItem.getTimerId() != null) {
return baseItem.getSeriesTimerId() != null ? TvApp.getApplication().getDrawableCompat(R.drawable.recseries) : TvApp.getApplication().getDrawableCompat(R.drawable.rec);
return baseItem.getSeriesTimerId() != null ? TvApp.getApplication().getDrawableCompat(R.drawable.ic_record_series_red) : TvApp.getApplication().getDrawableCompat(R.drawable.ic_record_red);
}
case Chapter:
break;
@@ -547,7 +539,6 @@ public class BaseRowItem {
public void refresh(final EmptyResponse outerResponse) {
switch (type) {
case BaseItem:
TvApp.getApplication().getApiClient().GetItemAsync(getItemId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
@@ -558,21 +549,13 @@ public class BaseRowItem {
});
break;
case Person:
break;
case Server:
break;
case User:
break;
case Chapter:
break;
case SearchHint:
break;
case LiveTvChannel:
break;
case LiveTvRecording:
break;
case GridButton:
break;
case LiveTvProgram:
break;
}
@@ -585,13 +568,28 @@ public class BaseRowItem {
public boolean isStaticHeight() {
return staticHeight;
}
public boolean isPlaying() { return isPlaying; }
public void setIsPlaying(boolean value) { isPlaying = value; }
public boolean isPlaying() {
return isPlaying;
}
public void setIsPlaying(boolean value) {
isPlaying = value;
}
public enum ItemType {
BaseItem,
Person,
Server, User, Chapter, SearchHint, LiveTvChannel, LiveTvRecording, GridButton, SeriesTimer, LiveTvProgram
Server,
User,
Chapter,
SearchHint,
LiveTvChannel,
LiveTvRecording,
GridButton,
SeriesTimer,
LiveTvProgram,
}
public enum SelectAction {
@@ -599,4 +597,3 @@ public class BaseRowItem {
Play
}
}

View File

@@ -12,7 +12,6 @@ import org.jellyfin.androidtv.browsing.BrowseScheduleActivity;
import org.jellyfin.androidtv.browsing.CollectionActivity;
import org.jellyfin.androidtv.browsing.GenericFolderActivity;
import org.jellyfin.androidtv.browsing.GenericGridActivity;
import org.jellyfin.androidtv.browsing.MainActivity;
import org.jellyfin.androidtv.browsing.UserViewActivity;
import org.jellyfin.androidtv.details.FullDetailsActivity;
import org.jellyfin.androidtv.details.ItemListActivity;
@@ -21,8 +20,6 @@ import org.jellyfin.androidtv.livetv.LiveTvGuideActivity;
import org.jellyfin.androidtv.model.ChapterItemInfo;
import org.jellyfin.androidtv.model.ViewType;
import org.jellyfin.androidtv.playback.MediaManager;
import org.jellyfin.androidtv.startup.SelectUserActivity;
import org.jellyfin.androidtv.util.DelayedMessage;
import org.jellyfin.androidtv.util.KeyProcessor;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
@@ -31,11 +28,9 @@ import org.jellyfin.androidtv.util.apiclient.PlaybackHelper;
import java.util.ArrayList;
import java.util.List;
import org.jellyfin.apiclient.interaction.ConnectionResult;
import org.jellyfin.apiclient.interaction.IConnectionManager;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.apiclient.ServerInfo;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.entities.DisplayPreferences;
import org.jellyfin.apiclient.model.library.PlayAccess;
@@ -56,27 +51,28 @@ public class ItemLauncher {
case BaseItem:
final BaseItemDto baseItem = rowItem.getBaseItem();
try {
TvApp.getApplication().getLogger().Debug("Item selected: " + rowItem.getIndex() + " - " + baseItem.getName() + " (" + baseItem.getType() + ")");
TvApp.getApplication().getLogger().Debug("Item selected: " + rowItem.getIndex() + " - " + baseItem.getName() + " (" + baseItem.getBaseItemType() + ")");
} catch (Exception e) {
//swallow it
}
//specialized type handling
switch (baseItem.getType()) {
case "UserView":
case "CollectionFolder":
switch (baseItem.getBaseItemType()) {
case UserView:
case CollectionFolder:
//We need to get display prefs...
TvApp.getApplication().getDisplayPrefsAsync(baseItem.getDisplayPreferencesId(), new Response<DisplayPreferences>() {
@Override
public void onResponse(DisplayPreferences response) {
if (baseItem.getCollectionType() == null)
if (baseItem.getCollectionType() == null) {
baseItem.setCollectionType("unknown");
TvApp.getApplication().getLogger().Debug("**** Collection type: "+baseItem.getCollectionType());
}
TvApp.getApplication().getLogger().Debug("**** Collection type: " + baseItem.getCollectionType());
switch (baseItem.getCollectionType()) {
case "movies":
case "tvshows":
case "music":
TvApp.getApplication().getLogger().Debug("**** View Type Pref: "+response.getCustomPrefs().get("DefaultView"));
TvApp.getApplication().getLogger().Debug("**** View Type Pref: " + response.getCustomPrefs().get("DefaultView"));
if (ViewType.GRID.equals(response.getCustomPrefs().get("DefaultView"))) {
// open grid browsing
Intent folderIntent = new Intent(activity, GenericGridActivity.class);
@@ -109,54 +105,62 @@ public class ItemLauncher {
}
});
return;
case "Series":
case "MusicArtist":
case Series:
case MusicArtist:
//Start activity for details display
Intent intent = new Intent(activity, FullDetailsActivity.class);
intent.putExtra("ItemId", baseItem.getId());
if (noHistory) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(intent);
return;
case "MusicAlbum":
case "Playlist":
case MusicAlbum:
case Playlist:
//Start activity for song list display
Intent songListIntent = new Intent(activity, ItemListActivity.class);
songListIntent.putExtra("ItemId", baseItem.getId());
if (noHistory) songListIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
songListIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(songListIntent);
return;
case "Audio":
case Audio:
//produce item menu
KeyProcessor.HandleKey(KeyEvent.KEYCODE_MENU, rowItem, (BaseActivity) activity);
return;
case "Season":
case "RecordingGroup":
case Season:
case RecordingGroup:
//Start activity for enhanced browse
Intent seasonIntent = new Intent(activity, GenericFolderActivity.class);
seasonIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(baseItem));
if (noHistory) seasonIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
seasonIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(seasonIntent);
return;
case "BoxSet":
case BoxSet:
// open collection browsing
Intent collectionIntent = new Intent(activity, CollectionActivity.class);
collectionIntent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(baseItem));
if (noHistory) collectionIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
collectionIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(collectionIntent);
return;
case "Photo":
case Photo:
// open photo player
MediaManager.setCurrentMediaPosition(pos);
Intent photoIntent = new Intent(activity, PhotoPlayerActivity.class);
@@ -174,7 +178,9 @@ public class ItemLauncher {
public void onResponse(DisplayPreferences response) {
Intent intent = new Intent(activity, GenericGridActivity.class);
intent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(baseItem));
if (noHistory) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(intent);
@@ -187,16 +193,18 @@ public class ItemLauncher {
//Start details fragment for display and playback
Intent intent = new Intent(activity, FullDetailsActivity.class);
intent.putExtra("ItemId", baseItem.getId());
if (noHistory) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(intent);
break;
case Play:
if (baseItem.getPlayAccess() == PlayAccess.Full) {
//Just play it directly
PlaybackHelper.getItemsToPlay(baseItem, baseItem.getType().equals("Movie"), false, new Response<List<BaseItemDto>>() {
PlaybackHelper.getItemsToPlay(baseItem, baseItem.getBaseItemType() == BaseItemType.Movie, false, new Response<List<BaseItemDto>>() {
@Override
public void onResponse(List<BaseItemDto> response) {
Intent intent = new Intent(activity,application.getPlaybackActivityClass(baseItem.getType()));
Intent intent = new Intent(activity, application.getPlaybackActivityClass(baseItem.getBaseItemType()));
MediaManager.setCurrentVideoQueue(response);
intent.putExtra("Position", 0);
activity.startActivity(intent);
@@ -213,7 +221,9 @@ public class ItemLauncher {
//Start details fragment
Intent intent = new Intent(activity, FullDetailsActivity.class);
intent.putExtra("ItemId", rowItem.getPerson().getId());
if (noHistory) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
if (noHistory) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(intent);
@@ -221,13 +231,13 @@ public class ItemLauncher {
case Chapter:
final ChapterItemInfo chapter = rowItem.getChapterInfo();
//Start playback of the item at the chapter point
application.getApiClient().GetItemAsync(chapter.getItemId(), application.getCurrentUser().getId(), new Response<BaseItemDto>(){
application.getApiClient().GetItemAsync(chapter.getItemId(), application.getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
List<BaseItemDto> items = new ArrayList<>();
items.add(response);
MediaManager.setCurrentVideoQueue(items);
Intent intent = new Intent(activity, application.getPlaybackActivityClass(response.getType()));
Intent intent = new Intent(activity, application.getPlaybackActivityClass(response.getBaseItemType()));
Long start = chapter.getStartPositionTicks() / 10000;
intent.putExtra("Position", start.intValue());
activity.startActivity(intent);
@@ -237,7 +247,7 @@ public class ItemLauncher {
break;
case Server:
//Log in to selected server
ServerSignIn(application.getConnectionManager(), rowItem.getServerInfo(), activity);
AuthenticationHelper.signInToServer(application.getConnectionManager(), rowItem.getServerInfo().getAddress(), activity);
break;
case User:
@@ -256,14 +266,14 @@ public class ItemLauncher {
application.getApiClient().GetItemAsync(hint.getItemId(), application.getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
if ((response.getIsFolderItem() && !"Series".equals(response.getType())) || "MusicArtist".equals(response.getType())) {
if ((response.getIsFolderItem() && response.getBaseItemType() != BaseItemType.Series) || response.getBaseItemType() == BaseItemType.MusicArtist) {
// open generic folder browsing
Intent intent = new Intent(activity, GenericGridActivity.class);
intent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(response));
activity.startActivity(intent);
} else if ("Audio".equals(response.getType())) {
} else if (response.getBaseItemType() == BaseItemType.Audio) {
PlaybackHelper.retrieveAndPlay(response.getId(), false, activity);
//produce item menu
// KeyProcessor.HandleKey(KeyEvent.KEYCODE_MENU, rowItem, (BaseActivity) activity);
@@ -272,8 +282,10 @@ public class ItemLauncher {
} else {
Intent intent = new Intent(activity, FullDetailsActivity.class);
intent.putExtra("ItemId", response.getId());
if (response.getType().equals("Program")) {
intent.putExtra("ItemType", response.getType());
if (response.getBaseItemType() == BaseItemType.Program) {
// TODO: Seems like this is never used...
intent.putExtra("ItemType", response.getBaseItemType().name());
intent.putExtra("ChannelId", response.getChannelId());
intent.putExtra("ProgramInfo", TvApp.getApplication().getSerializer().SerializeToString(response));
}
@@ -296,11 +308,14 @@ public class ItemLauncher {
//Start details fragment for display and playback
Intent programIntent = new Intent(activity, FullDetailsActivity.class);
programIntent.putExtra("ItemId", program.getId());
programIntent.putExtra("ItemType", program.getType());
// TODO Seems unused
programIntent.putExtra("ItemType", program.getBaseItemType().name());
programIntent.putExtra("ChannelId", program.getChannelId());
programIntent.putExtra("ProgramInfo", TvApp.getApplication().getSerializer().SerializeToString(program));
activity.startActivity(programIntent);
activity.startActivity(programIntent);
break;
case Play:
if (program.getPlayAccess() == PlayAccess.Full) {
@@ -310,7 +325,7 @@ public class ItemLauncher {
public void onResponse(BaseItemDto response) {
List<BaseItemDto> items = new ArrayList<>();
items.add(response);
Intent intent = new Intent(activity, TvApp.getApplication().getPlaybackActivityClass(response.getType()));
Intent intent = new Intent(activity, TvApp.getApplication().getPlaybackActivityClass(response.getBaseItemType()));
MediaManager.setCurrentVideoQueue(items);
intent.putExtra("Position", 0);
activity.startActivity(intent);
@@ -332,7 +347,8 @@ public class ItemLauncher {
PlaybackHelper.getItemsToPlay(response, false, false, new Response<List<BaseItemDto>>() {
@Override
public void onResponse(List<BaseItemDto> response) {
Intent intent = new Intent(activity, application.getPlaybackActivityClass(channel.getType()));
// TODO Check whether this usage of BaseItemType.valueOf is okay.
Intent intent = new Intent(activity, application.getPlaybackActivityClass(BaseItemType.valueOf(channel.getType())));
MediaManager.setCurrentVideoQueue(response);
intent.putExtra("Position", 0);
activity.startActivity(intent);
@@ -359,7 +375,7 @@ public class ItemLauncher {
TvApp.getApplication().getApiClient().GetItemAsync(rowItem.getRecordingInfo().getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
Intent intent = new Intent(activity, application.getPlaybackActivityClass(rowItem.getType()));
Intent intent = new Intent(activity, application.getPlaybackActivityClass(rowItem.getBaseItemType()));
List<BaseItemDto> items = new ArrayList<>();
items.add(response);
MediaManager.setCurrentVideoQueue(items);
@@ -433,63 +449,8 @@ public class ItemLauncher {
Intent schedIntent = new Intent(activity, BrowseScheduleActivity.class);
activity.startActivity(schedIntent);
break;
}
break;
}
}
public static void ServerSignIn(final IConnectionManager connectionManager, final ServerInfo serverInfo, final Activity activity) {
//Connect to the selected server
final DelayedMessage message = new DelayedMessage(activity);
connectionManager.Connect(serverInfo, new Response<ConnectionResult>() {
@Override
public void onResponse(ConnectionResult response) {
message.Cancel();
switch (response.getState()) {
case Unavailable:
Utils.showToast(activity, "Server unavailable");
break;
case SignedIn:
if (serverInfo.getUserLinkType() != null) {
// go straight in for connect only
response.getApiClient().GetUserAsync(serverInfo.getUserId(), new Response<UserDto>() {
@Override
public void onResponse(UserDto response) {
TvApp.getApplication().setCurrentUser(response);
Intent homeIntent = new Intent(activity, MainActivity.class);
activity.startActivity(homeIntent);
}
});
break;
}
case ServerSignIn:
//Set api client for login
TvApp.getApplication().setLoginApiClient(response.getApiClient());
//Open user selection
Intent userIntent = new Intent(activity, SelectUserActivity.class);
userIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
activity.startActivity(userIntent);
break;
case ConnectSignIn:
case ServerSelection:
Utils.showToast(activity, "Unexpected response from server connect: " + response.getState());
break;
}
}
@Override
public void onError(Exception exception) {
message.Cancel();
Utils.showToast(activity, "Error Signing in to server");
exception.printStackTrace();
Utils.reportError(activity, "Error Signing in to server");
}
});
}
}

View File

@@ -1,11 +1,12 @@
package org.jellyfin.androidtv.itemhandling;
import android.os.Handler;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.PresenterSelector;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
@@ -37,6 +38,7 @@ import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.apiclient.ServerInfo;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemPerson;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.livetv.ChannelInfoDto;
import org.jellyfin.apiclient.model.livetv.LiveTvChannelQuery;
@@ -62,9 +64,6 @@ import org.jellyfin.apiclient.model.search.SearchHint;
import org.jellyfin.apiclient.model.search.SearchHintResult;
import org.jellyfin.apiclient.model.search.SearchQuery;
/**
* Created by Eric on 12/5/2014.
*/
public class ItemRowAdapter extends ArrayObjectAdapter {
private ItemQuery mQuery;
private NextUpQuery mNextUpQuery;
@@ -90,7 +89,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
private EmptyResponse mRetrieveStartedListener;
private EmptyResponse mRetrieveFinishedListener;
private ChangeTriggerType[] reRetrieveTriggers = new ChangeTriggerType[] {};
private ChangeTriggerType[] reRetrieveTriggers = new ChangeTriggerType[]{};
private Calendar lastFullRetrieve;
private BaseItemPerson[] mPersons;
@@ -123,11 +122,21 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
}
public boolean getPreferParentThumb() { return preferParentThumb; }
public boolean isStaticHeight() { return staticHeight; }
public QueryType getQueryType() { return queryType; }
public boolean getPreferParentThumb() {
return preferParentThumb;
}
public ArrayObjectAdapter getParent() { return mParent; }
public boolean isStaticHeight() {
return staticHeight;
}
public QueryType getQueryType() {
return queryType;
}
public ArrayObjectAdapter getParent() {
return mParent;
}
public void setRow(ListRow row) {
mRow = row;
@@ -136,6 +145,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void setReRetrieveTriggers(ChangeTriggerType[] reRetrieveTriggers) {
this.reRetrieveTriggers = reRetrieveTriggers;
}
public ItemRowAdapter(ItemQuery query, int chunkSize, boolean preferParentThumb, Presenter presenter, ArrayObjectAdapter parent) {
this(query, chunkSize, preferParentThumb, false, presenter, parent);
}
@@ -148,7 +158,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
this.chunkSize = chunkSize;
this.preferParentThumb = preferParentThumb;
this.staticHeight = staticHeight;
if (chunkSize > 0) mQuery.setLimit(chunkSize);
if (chunkSize > 0) {
mQuery.setLimit(chunkSize);
}
this.queryType = queryType;
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
@@ -161,7 +173,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
this.chunkSize = chunkSize;
this.preferParentThumb = preferParentThumb;
this.staticHeight = staticHeight;
if (chunkSize > 0) mQuery.setLimit(chunkSize);
if (chunkSize > 0) {
mQuery.setLimit(chunkSize);
}
this.queryType = queryType;
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
@@ -177,7 +191,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mArtistsQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
staticHeight = true;
this.chunkSize = chunkSize;
if (chunkSize > 0) mArtistsQuery.setLimit(chunkSize);
if (chunkSize > 0) {
mArtistsQuery.setLimit(chunkSize);
}
queryType = QueryType.AlbumArtists;
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
@@ -190,7 +206,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
queryType = QueryType.NextUp;
this.preferParentThumb = preferParentThumb;
this.staticHeight = true;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(SeriesTimerQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -198,7 +214,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mParent = parent;
mSeriesTimerQuery = query;
queryType = QueryType.SeriesTimer;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(LatestItemsQuery query, boolean preferParentThumb, Presenter presenter, ArrayObjectAdapter parent) {
@@ -208,7 +224,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mLatestQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
queryType = QueryType.LatestItems;
this.preferParentThumb = preferParentThumb;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(BaseItemPerson[] people, Presenter presenter, ArrayObjectAdapter parent) {
@@ -253,7 +269,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mParent = parent;
mSpecialsQuery = query;
queryType = QueryType.Specials;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(TrailersQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -261,7 +277,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mParent = parent;
mTrailersQuery = query;
queryType = QueryType.Trailers;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(LiveTvChannelQuery query, int chunkSize, Presenter presenter, ArrayObjectAdapter parent) {
@@ -269,9 +285,11 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mParent = parent;
mTvChannelQuery = query;
this.chunkSize = chunkSize;
if (chunkSize > 0) mTvChannelQuery.setLimit(chunkSize);
if (chunkSize > 0) {
mTvChannelQuery.setLimit(chunkSize);
}
queryType = QueryType.LiveTvChannel;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(RecommendedProgramQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -280,7 +298,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mTvProgramQuery = query;
queryType = QueryType.LiveTvProgram;
staticHeight = true;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(RecordingQuery query, int chunkSize, Presenter presenter, ArrayObjectAdapter parent) {
@@ -290,7 +308,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
this.chunkSize = chunkSize;
queryType = QueryType.LiveTvRecording;
staticHeight = true;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(RecordingGroupQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -298,7 +316,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mParent = parent;
mTvRecordingGroupQuery = query;
queryType = QueryType.LiveTvRecordingGroup;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(SimilarItemsQuery query, QueryType queryType, Presenter presenter, ArrayObjectAdapter parent) {
@@ -307,7 +325,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mSimilarQuery = query;
mSimilarQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
this.queryType = queryType;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(UpcomingEpisodesQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -316,7 +334,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mUpcomingQuery = query;
mUpcomingQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
queryType = QueryType.Upcoming;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(SeasonQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -325,7 +343,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mSeasonQuery = query;
mSeasonQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
queryType = QueryType.Season;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(PersonsQuery query, int chunkSize, Presenter presenter, ArrayObjectAdapter parent) {
@@ -334,9 +352,11 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
this.chunkSize = chunkSize;
mPersonsQuery = query;
mPersonsQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
if (chunkSize > 0) mPersonsQuery.setLimit(chunkSize);
if (chunkSize > 0) {
mPersonsQuery.setLimit(chunkSize);
}
queryType = QueryType.Persons;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(SearchQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -346,7 +366,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mSearchQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
mSearchQuery.setLimit(50);
queryType = QueryType.Search;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(ViewQuery query, Presenter presenter, ArrayObjectAdapter parent) {
@@ -354,7 +374,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mParent = parent;
queryType = QueryType.Views;
staticHeight = true;
add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading)));
}
public ItemRowAdapter(ServerInfo serverInfo, Presenter presenter, ArrayObjectAdapter parent) {
@@ -383,7 +403,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
totalItems = amt;
}
public int getTotalItems() { return totalItems; }
public int getTotalItems() {
return totalItems;
}
public void setSortBy(HorizontalGridFragment.SortOption option) {
if (!option.value.equals(mSortBy)) {
@@ -394,53 +416,69 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
mArtistsQuery.setSortOrder(option.order);
break;
default:
mQuery.setSortBy(new String[] {mSortBy});
mQuery.setSortBy(new String[]{mSortBy});
mQuery.setSortOrder(option.order);
break;
}
if (!"SortName".equals(option.value)) setStartLetter(null);
if (!"SortName".equals(option.value)) {
setStartLetter(null);
}
}
}
public BaseRowItem findByIndex(int ndx) {
//search for actual index number and return matching item
for (int i = 0; i < getItemsLoaded(); i++) {
BaseRowItem item = (BaseRowItem)this.get(i);
if (item.getIndex() == ndx) return item;
BaseRowItem item = (BaseRowItem) this.get(i);
if (item.getIndex() == ndx) {
return item;
}
}
return null;
}
public String getSortBy() { return mSortBy; }
public String getSortBy() {
return mSortBy;
}
public FilterOptions getFilters() { return mFilters; }
public FilterOptions getFilters() {
return mFilters;
}
public void setFilters(FilterOptions filters) {
mFilters = filters;
switch (queryType) {
case AlbumArtists:
mArtistsQuery.setFilters(mFilters != null ? mFilters.getFilters() : null);
break;
default:
mQuery.setFilters(mFilters != null ? mFilters.getFilters() : null);
}
switch (queryType) {
case AlbumArtists:
mArtistsQuery.setFilters(mFilters != null ? mFilters.getFilters() : null);
break;
default:
mQuery.setFilters(mFilters != null ? mFilters.getFilters() : null);
}
}
public void setPosition(int pos) {
((IPositionablePresenter)(getParent().getPresenter(this))).setPosition(pos);
((IPositionablePresenter) (getParent().getPresenter(this))).setPosition(pos);
}
public String getStartLetter() { return mQuery != null ? mQuery.getNameStartsWithOrGreater() : null; }
public String getStartLetter() {
return mQuery != null ? mQuery.getNameStartsWithOrGreater() : null;
}
public void setStartLetter(String value) {
switch (queryType) {
case AlbumArtists:
if (value != null && value.equals("#")) mArtistsQuery.setNameStartsWithOrGreater(null);
else mArtistsQuery.setNameStartsWithOrGreater(value);
if (value != null && value.equals("#")) {
mArtistsQuery.setNameStartsWithOrGreater(null);
} else {
mArtistsQuery.setNameStartsWithOrGreater(value);
}
break;
default:
if (value != null && value.equals("#")) mQuery.setNameStartsWithOrGreater(null);
else mQuery.setNameStartsWithOrGreater(value);
if (value != null && value.equals("#")) {
mQuery.setNameStartsWithOrGreater(null);
} else {
mQuery.setNameStartsWithOrGreater(value);
}
break;
}
}
@@ -450,7 +488,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
// just clear us
clear();
return;
};
}
if (mParent.size() == 1) {
// we will be removing the last row - show something and prevent the framework from crashing
@@ -483,7 +521,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void RetrieveNext() {
switch (queryType) {
case Persons:
if (fullyLoaded || mPersonsQuery == null || isCurrentlyRetrieving()) return;
if (fullyLoaded || mPersonsQuery == null || isCurrentlyRetrieving()) {
return;
}
setCurrentlyRetrieving(true);
//set the query to go get the next chunk
@@ -492,7 +532,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
break;
case LiveTvChannel:
if (fullyLoaded || mTvChannelQuery == null || isCurrentlyRetrieving()) return;
if (fullyLoaded || mTvChannelQuery == null || isCurrentlyRetrieving()) {
return;
}
setCurrentlyRetrieving(true);
//set the query to go get the next chunk
@@ -501,7 +543,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
break;
case AlbumArtists:
if (fullyLoaded || mArtistsQuery == null || isCurrentlyRetrieving()) return;
if (fullyLoaded || mArtistsQuery == null || isCurrentlyRetrieving()) {
return;
}
setCurrentlyRetrieving(true);
//set the query to go get the next chunk
@@ -510,7 +554,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
break;
default:
if (fullyLoaded || mQuery == null || isCurrentlyRetrieving()) return;
if (fullyLoaded || mQuery == null || isCurrentlyRetrieving()) {
return;
}
setCurrentlyRetrieving(true);
//set the query to go get the next chunk
@@ -521,7 +567,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
public boolean ReRetrieveIfNeeded() {
if (reRetrieveTriggers == null) return false;
if (reRetrieveTriggers == null) {
return false;
}
boolean retrieve = false;
TvApp app = TvApp.getApplication();
@@ -558,7 +606,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
if (retrieve) {
TvApp.getApplication().getLogger().Info("Re-retrieving row of type "+ queryType);
TvApp.getApplication().getLogger().Info("Re-retrieving row of type " + queryType);
Retrieve();
}
@@ -665,7 +713,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
totalItems = response.length;
setItemsLoaded(totalItems);
if (totalItems == 0) removeRow();
if (totalItems == 0) {
removeRow();
}
currentlyRetrieving = false;
}
@@ -748,7 +798,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
currentlyRetrieving = false;
}
private static String[] ignoreTypes = new String[] {"books","games"};
private static String[] ignoreTypes = new String[]{"books", "games"};
private static List<String> ignoreTypeList = Arrays.asList(ignoreTypes);
private void RetrieveViews() {
@@ -763,8 +813,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
for (BaseItemDto item : response.getItems()) {
//re-map the display prefs id to our actual id
item.setDisplayPreferencesId(item.getId());
if (!ignoreTypeList.contains(item.getCollectionType()) && !ignoreTypeList.contains(item.getType()))
if (!ignoreTypeList.contains(item.getCollectionType())) {
adapter.add(new BaseRowItem(i++, item, preferParentThumb, staticHeight));
}
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(itemsLoaded + i);
@@ -799,7 +850,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(SearchHintResult response) {
if (response.getSearchHints() != null && response.getSearchHints().length > 0) {
int i = 0;
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
for (SearchHint item : response.getSearchHints()) {
if (!ignoreTypeList.contains(item.getType())) {
i++;
@@ -808,10 +861,10 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(itemsLoaded + i);
if (itemsLoaded > 0 && mParent != null) mParent.add(mRow);
}
currentlyRetrieving = false;
notifyRetrieveFinished();
}
@Override
@@ -819,11 +872,18 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
TvApp.getApplication().getLogger().ErrorException("Error retrieving search results", exception);
Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage());
currentlyRetrieving = false;
notifyRetrieveFinished();
}
});
}
public void addToParentIfResultsReceived() {
if (itemsLoaded > 0 && mParent != null) {
mParent.add(mRow);
}
}
public void GetResultSizeAsync(final Response<Integer> outerResponse) {
switch (queryType) {
case AlbumArtists:
@@ -895,7 +955,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
} else {
// no results - don't show us
if (getItemsLoaded() == 0) removeRow();
if (getItemsLoaded() == 0) {
removeRow();
}
}
setCurrentlyRetrieving(false);
@@ -934,7 +996,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void RetrieveAudioPlaylists(final ItemQuery query) {
//Add specialized playlists first
clear();
add(new GridButton(EnhancedBrowseFragment.FAVSONGS, TvApp.getApplication().getString(R.string.lbl_favorites), R.drawable.genericmusic));
add(new GridButton(EnhancedBrowseFragment.FAVSONGS, TvApp.getApplication().getString(R.string.lbl_favorites), R.drawable.favorites));
itemsLoaded = 1;
Retrieve(query);
}
@@ -944,7 +1006,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
clear();
if (MediaManager.hasVideoQueueItems()) {
TvApp.getApplication().getLogger().Debug("Adding video queue...");
add(new BaseRowItem(new GridButton(TvApp.VIDEO_QUEUE_OPTION_ID, TvApp.getApplication().getString(R.string.lbl_current_queue), R.drawable.videoqueue)));
add(new BaseRowItem(new GridButton(TvApp.VIDEO_QUEUE_OPTION_ID, TvApp.getApplication().getString(R.string.lbl_current_queue), R.drawable.tile_port_video_queue)));
itemsLoaded = 1;
}
Retrieve(query);
@@ -1029,7 +1091,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
if (response.getItems() != null && response.getItems().length > 0) {
int i = 0;
Calendar compare = Calendar.getInstance();
@@ -1038,7 +1102,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
for (BaseItemDto item : response.getItems()) {
if (item.getIndexNumber() != null && item.getIndexNumber() == 1 && (item.getDateCreated() == null || item.getDateCreated().after(compare.getTime()))
&& (item.getUserData() == null || item.getUserData().getLikes() == null || item.getUserData().getLikes())
) {
) {
// new unwatched episode 1 not disliked - check to be sure prev episode not already in next up
BaseItemDto nextUpItem = null;
for (int n = 0; n < nextUpItems.length; n++) {
@@ -1053,8 +1117,8 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
BaseRowItem existing = null;
int existingPos = -1;
for (int n = 0; n < adapter.size(); n++) {
if (((BaseRowItem)adapter.get(n)).getBaseItem().getSeriesId().equals(item.getSeriesId())) {
existing = (BaseRowItem)adapter.get(n);
if (((BaseRowItem) adapter.get(n)).getBaseItem().getSeriesId().equals(item.getSeriesId())) {
existing = (BaseRowItem) adapter.get(n);
existingPos = n;
break;
}
@@ -1078,7 +1142,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
}
if (adapter.size() == 0) removeRow();
if (adapter.size() == 0) {
removeRow();
}
currentlyRetrieving = false;
}
});
@@ -1095,7 +1161,9 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
@Override
public void onResponse(final ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
int i = 0;
for (BaseItemDto item : response.getItems()) {
adapter.add(new BaseRowItem(i++, item, preferParentThumb, staticHeight));
@@ -1108,7 +1176,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
} else {
//If this was for a single series, get the rest of the episodes in the season
if (query.getSeriesId() != null) {
BaseItemDto first = adapter.size() == 1 ? ((BaseRowItem)adapter.get(0)).getBaseItem() : null;
BaseItemDto first = adapter.size() == 1 ? ((BaseRowItem) adapter.get(0)).getBaseItem() : null;
if (first != null && first.getIndexNumber() != null && first.getSeasonId() != null) {
StdItemQuery rest = new StdItemQuery();
rest.setUserId(query.getUserId());
@@ -1165,14 +1233,18 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(ChannelInfoDtoResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
int i = itemsLoaded;
if (i == 0 && adapter.size() > 0) adapter.clear();
if (i == 0 && adapter.size() > 0) {
adapter.clear();
}
for (ChannelInfoDto item : response.getItems()) {
adapter.add(new BaseRowItem(i, item));
i++;
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();
@@ -1201,11 +1273,6 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
if (response.getItems() != null && response.getItems().length > 0) {
int i = 0;
int prevItems = adapter.size() > 0 ? adapter.size() : 0;
if (query.getIsAiring()) {
// show guide option as first item
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_GUIDE_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_live_tv_guide), R.drawable.guide)));
i++;
}
for (BaseItemDto item : response.getItems()) {
adapter.add(new BaseRowItem(item, staticHeight));
i++;
@@ -1248,7 +1315,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
int i = 0;
int prevItems = adapter.size() > 0 ? adapter.size() : 0;
for (BaseItemDto item : response.getItems()) {
item.setType("RecordingGroup"); // the API does not fill this in
item.setBaseItemType(BaseItemType.RecordingGroup); // the API does not fill this in
item.setIsFolder(true); // nor this
adapter.add(new BaseRowItem(item));
i++;
@@ -1331,14 +1398,14 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
int prevItems = adapter.size() > 0 ? adapter.size() : 0;
if (adapter.chunkSize == 0) {
// and recordings as first item if showing all
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_RECORDINGS_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_recorded_tv), R.drawable.recgroup)));
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_RECORDINGS_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_recorded_tv), R.drawable.tile_port_record)));
i++;
if (TvApp.getApplication().canManageRecordings()) {
// and schedule
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_SCHEDULE_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_schedule), R.drawable.clock)));
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_SCHEDULE_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_schedule), R.drawable.tile_port_time)));
i++;
// and series
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_SERIES_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_series), R.drawable.seriestimerp)));
adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_SERIES_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_series), R.drawable.tile_port_series_timer)));
i++;
}
}
@@ -1382,13 +1449,17 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(BaseItemDto[] response) {
if (response.length > 0) {
int i = 0;
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
for (BaseItemDto item : response) {
adapter.add(new BaseRowItem(i++, item, preferParentThumb, false));
}
totalItems = response.length;
setItemsLoaded(itemsLoaded + i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();
@@ -1415,14 +1486,18 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(BaseItemDto[] response) {
if (response.length > 0) {
int i = 0;
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
for (BaseItemDto item : response) {
item.setName(TvApp.getApplication().getString(R.string.lbl_trailer) + (i + 1));
adapter.add(new BaseRowItem(i++, item, preferParentThumb, false, BaseRowItem.SelectAction.Play));
}
totalItems = response.length;
setItemsLoaded(itemsLoaded + i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();
@@ -1449,13 +1524,17 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
int i = 0;
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
for (BaseItemDto item : response.getItems()) {
adapter.add(new BaseRowItem(i++, item));
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(itemsLoaded + i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();
@@ -1482,13 +1561,17 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
int i = 0;
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
for (BaseItemDto item : response.getItems()) {
adapter.add(new BaseRowItem(i++, item));
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(itemsLoaded + i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();
@@ -1515,14 +1598,19 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
int i = 0;
if (adapter.size() > 0) adapter.clear();
if (adapter.size() > 0) {
adapter.clear();
}
for (BaseItemDto item : response.getItems()) {
if (query.getParentId() == null || item.getSeriesId() == null || item.getSeriesId().equals(query.getParentId()))
if (query.getParentId() == null || item.getSeriesId() == null || item.getSeriesId().equals(query.getParentId())) {
adapter.add(new BaseRowItem(i++, item));
}
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(itemsLoaded + i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();
@@ -1549,13 +1637,17 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
public void onResponse(ItemsResult response) {
if (response.getItems() != null && response.getItems().length > 0) {
int i = itemsLoaded;
if (i == 0 && adapter.size() > 0) adapter.clear();
if (i == 0 && adapter.size() > 0) {
adapter.clear();
}
for (BaseItemDto item : response.getItems()) {
adapter.add(new BaseRowItem(i++, item));
}
totalItems = response.getTotalRecordCount();
setItemsLoaded(i);
if (i == 0) removeRow();
if (i == 0) {
removeRow();
}
} else {
// no results - don't show us
removeRow();

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -425,7 +425,7 @@ public class LiveTvGuideActivity extends BaseActivity implements ILiveTvGuide {
mPopup = new PopupWindow(layout, WIDTH, HEIGHT);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new BitmapDrawable()); // necessary for popup to dismiss
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mPopup.setAnimationStyle(R.style.PopupSlideInRight);
mMovies = layout.findViewById(R.id.movies);
mSeries = layout.findViewById(R.id.series);
@@ -511,7 +511,7 @@ public class LiveTvGuideActivity extends BaseActivity implements ILiveTvGuide {
mPopup = new PopupWindow(layout, WIDTH, HEIGHT);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new BitmapDrawable()); // necessary for popup to dismiss
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mPopup.setAnimationStyle(R.style.PopupSlideInRight);
mHd = layout.findViewById(R.id.hd);
mRepeat = layout.findViewById(R.id.repeat);
@@ -925,23 +925,23 @@ public class LiveTvGuideActivity extends BaseActivity implements ILiveTvGuide {
InfoLayoutHelper.addInfoRow(mActivity, mSelectedProgram, mInfoRow, false, false);
if (Utils.isTrue(mSelectedProgram.getIsNews())) {
mBackdrop.setImageResource(R.drawable.newsbanner);
mBackdrop.setImageResource(R.drawable.banner_news);
} else if (Utils.isTrue(mSelectedProgram.getIsKids())) {
mBackdrop.setImageResource(R.drawable.kidsbanner);
mBackdrop.setImageResource(R.drawable.banner_kids);
} else if (Utils.isTrue(mSelectedProgram.getIsSports())) {
mBackdrop.setImageResource(R.drawable.sportsbanner);
mBackdrop.setImageResource(R.drawable.banner_sports);
} else if (Utils.isTrue(mSelectedProgram.getIsMovie())) {
mBackdrop.setImageResource(R.drawable.moviebanner);
mBackdrop.setImageResource(R.drawable.banner_movie);
} else {
mBackdrop.setImageResource(R.drawable.tvbanner);
mBackdrop.setImageResource(R.drawable.banner_tv);
}
} else {
mInfoRow.removeAllViews();
mBackdrop.setImageResource(R.drawable.tvbanner);
mBackdrop.setImageResource(R.drawable.banner_tv);
mImage.setImageResource(R.drawable.blank10x10);
}
}

View File

@@ -2,10 +2,10 @@ package org.jellyfin.androidtv.livetv;
import android.app.Activity;
import android.graphics.Typeface;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.Presenter;
import android.text.format.DateUtils;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -32,6 +32,7 @@ import java.util.TimeZone;
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.BaseItemType;
import org.jellyfin.apiclient.model.entities.DisplayPreferences;
import org.jellyfin.apiclient.model.entities.LocationType;
import org.jellyfin.apiclient.model.entities.SortOrder;
@@ -405,7 +406,7 @@ public class TvManager {
programInfo.setChannelName(timer.getChannelName());
programInfo.setName(Utils.getSafeValue(timer.getName(), "Unknown"));
TvApp.getApplication().getLogger().Warn("No program info for timer %s. Creating one...", programInfo.getName());
programInfo.setType("Program");
programInfo.setBaseItemType(BaseItemType.Program);
programInfo.setTimerId(timer.getId());
programInfo.setSeriesTimerId(timer.getSeriesTimerId());
programInfo.setStartDate(timer.getStartDate());

View File

@@ -1,11 +1,8 @@
package org.jellyfin.androidtv.startup;
package org.jellyfin.androidtv.model;
import org.jellyfin.apiclient.model.apiclient.ServerInfo;
import org.jellyfin.apiclient.model.dto.UserDto;
/**
* Created by Eric on 1/20/2015.
*/
public class LogonCredentials {
private ServerInfo serverInfo;
private UserDto userDto;
@@ -30,5 +27,4 @@ public class LogonCredentials {
public void setUserDto(UserDto userDto) {
this.userDto = userDto;
}
}

View File

@@ -3,6 +3,10 @@ package org.jellyfin.androidtv.model.compat;
import java.util.ArrayList;
import java.util.Arrays;
import org.jellyfin.androidtv.constants.CodecTypes;
import org.jellyfin.androidtv.constants.ContainerTypes;
import org.jellyfin.androidtv.constants.MediaTypes;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.model.dlna.CodecProfile;
import org.jellyfin.apiclient.model.dlna.CodecType;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
@@ -16,7 +20,6 @@ import org.jellyfin.apiclient.model.dlna.ResponseProfile;
import org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod;
import org.jellyfin.apiclient.model.dlna.SubtitleProfile;
import org.jellyfin.apiclient.model.dlna.TranscodingProfile;
import org.jellyfin.apiclient.model.extensions.StringHelper;
@Deprecated
public class AndroidProfile extends DeviceProfile {
@@ -40,50 +43,50 @@ public class AndroidProfile extends DeviceProfile {
ArrayList<TranscodingProfile> transcodingProfiles = new ArrayList<>();
TranscodingProfile tempVar = new TranscodingProfile();
tempVar.setContainer("mp3");
tempVar.setAudioCodec("mp3");
tempVar.setContainer(CodecTypes.MP3);
tempVar.setAudioCodec(CodecTypes.MP3);
tempVar.setType(DlnaProfileType.Audio);
tempVar.setContext(EncodingContext.Streaming);
transcodingProfiles.add(tempVar);
TranscodingProfile tempVar0 = new TranscodingProfile();
tempVar0.setContainer("mp3");
tempVar0.setAudioCodec("mp3");
tempVar0.setContainer(CodecTypes.MP3);
tempVar0.setAudioCodec(CodecTypes.MP3);
tempVar0.setType(DlnaProfileType.Audio);
tempVar0.setContext(EncodingContext.Static);
transcodingProfiles.add(tempVar0);
if (profileOptions.SupportsHls) {
TranscodingProfile tempVar2 = new TranscodingProfile();
tempVar2.setProtocol("hls");
tempVar2.setContainer("ts");
tempVar2.setVideoCodec("h264");
tempVar2.setAudioCodec("aac");
tempVar2.setProtocol(MediaTypes.HLS);
tempVar2.setContainer(ContainerTypes.TS);
tempVar2.setVideoCodec(CodecTypes.H264);
tempVar2.setAudioCodec(CodecTypes.AAC);
tempVar2.setType(DlnaProfileType.Video);
tempVar2.setContext(EncodingContext.Streaming);
transcodingProfiles.add(tempVar2);
}
TranscodingProfile mkvProfile = new TranscodingProfile();
mkvProfile.setContainer("mkv");
mkvProfile.setVideoCodec("h264");
mkvProfile.setAudioCodec("aac,mp3");
mkvProfile.setContainer(ContainerTypes.MKV);
mkvProfile.setVideoCodec(CodecTypes.H264);
mkvProfile.setAudioCodec(Utils.join(",", CodecTypes.AAC, CodecTypes.MP3));
mkvProfile.setType(DlnaProfileType.Video);
mkvProfile.setContext(EncodingContext.Streaming);
mkvProfile.setCopyTimestamps(true);
transcodingProfiles.add(mkvProfile);
TranscodingProfile tempVar3 = new TranscodingProfile();
tempVar3.setContainer("mp4");
tempVar3.setVideoCodec("h264");
tempVar3.setAudioCodec("aac");
tempVar3.setContainer(ContainerTypes.MP4);
tempVar3.setVideoCodec(CodecTypes.H264);
tempVar3.setAudioCodec(CodecTypes.AAC);
tempVar3.setType(DlnaProfileType.Video);
tempVar3.setContext(EncodingContext.Static);
transcodingProfiles.add(tempVar3);
TranscodingProfile webmProfile = new TranscodingProfile();
webmProfile.setContainer("webm");
webmProfile.setVideoCodec("vpx");
webmProfile.setAudioCodec("vorbis");
webmProfile.setContainer(ContainerTypes.WEBM);
webmProfile.setVideoCodec(CodecTypes.VPX);
webmProfile.setAudioCodec(CodecTypes.VORBIS);
webmProfile.setType(DlnaProfileType.Video);
webmProfile.setContext(EncodingContext.Streaming);
transcodingProfiles.add(webmProfile);
@@ -91,26 +94,31 @@ public class AndroidProfile extends DeviceProfile {
setTranscodingProfiles(transcodingProfiles.toArray(new TranscodingProfile[0]));
DirectPlayProfile tempVar4 = new DirectPlayProfile();
tempVar4.setContainer("mp4");
tempVar4.setVideoCodec("h264,mpeg4");
tempVar4.setAudioCodec("aac");
tempVar4.setContainer(ContainerTypes.MP4);
tempVar4.setVideoCodec(Utils.join(",", CodecTypes.H264, CodecTypes.MPEG4));
tempVar4.setAudioCodec(CodecTypes.AAC);
tempVar4.setType(DlnaProfileType.Video);
DirectPlayProfile tempVar5 = new DirectPlayProfile();
tempVar5.setContainer("mp4,aac");
tempVar5.setAudioCodec("aac");
tempVar5.setContainer(Utils.join(",", ContainerTypes.MP4, CodecTypes.AAC));
tempVar5.setAudioCodec(CodecTypes.AAC);
tempVar5.setType(DlnaProfileType.Audio);
DirectPlayProfile tempVar6 = new DirectPlayProfile();
tempVar6.setContainer("mp3");
tempVar6.setAudioCodec("mp3");
tempVar6.setContainer(CodecTypes.MP3);
tempVar6.setAudioCodec(CodecTypes.MP3);
tempVar6.setType(DlnaProfileType.Audio);
DirectPlayProfile tempVar7 = new DirectPlayProfile();
tempVar7.setContainer("flac");
tempVar7.setAudioCodec("flac");
tempVar7.setContainer(CodecTypes.FLAC);
tempVar7.setAudioCodec(CodecTypes.FLAC);
tempVar7.setType(DlnaProfileType.Audio);
DirectPlayProfile tempVar8 = new DirectPlayProfile();
tempVar8.setContainer("ogg");
tempVar8.setAudioCodec("vorbis");
tempVar8.setContainer(ContainerTypes.OGG);
tempVar8.setAudioCodec(CodecTypes.VORBIS);
tempVar8.setType(DlnaProfileType.Audio);
DirectPlayProfile tempVar9 = new DirectPlayProfile();
tempVar9.setContainer("jpeg,png,gif,bmp");
tempVar9.setType(DlnaProfileType.Photo);
@@ -118,7 +126,7 @@ public class AndroidProfile extends DeviceProfile {
CodecProfile tempVar10 = new CodecProfile();
tempVar10.setType(CodecType.Video);
tempVar10.setCodec("h264");
tempVar10.setCodec(CodecTypes.H264);
tempVar10.setConditions(new ProfileCondition[]
{
new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, profileOptions.DefaultH264Profile),
@@ -134,7 +142,7 @@ public class AndroidProfile extends DeviceProfile {
CodecProfile tempVar11 = new CodecProfile();
tempVar11.setType(CodecType.Video);
tempVar11.setCodec("vpx,mpeg4");
tempVar11.setCodec(Utils.join(",", CodecTypes.VPX, CodecTypes.MPEG4));
tempVar11.setConditions(new ProfileCondition[]
{
new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.Width, "1920"),
@@ -145,17 +153,17 @@ public class AndroidProfile extends DeviceProfile {
CodecProfile tempVar12 = new CodecProfile();
tempVar12.setType(CodecType.VideoAudio);
tempVar12.setCodec("aac");
tempVar12.setCodec(CodecTypes.AAC);
tempVar12.setConditions(new ProfileCondition[]{new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.AudioChannels, "2")});
CodecProfile tempVar13 = new CodecProfile();
tempVar13.setType(CodecType.Audio);
tempVar13.setCodec("aac");
tempVar13.setCodec(CodecTypes.AAC);
tempVar13.setConditions(new ProfileCondition[]{new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.AudioChannels, "2")});
CodecProfile tempVar14 = new CodecProfile();
tempVar14.setType(CodecType.Audio);
tempVar14.setCodec("mp3");
tempVar14.setCodec(CodecTypes.MP3);
tempVar14.setConditions(new ProfileCondition[]
{
new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.AudioChannels, "2"),
@@ -182,15 +190,15 @@ public class AndroidProfile extends DeviceProfile {
for (DirectPlayProfile profile : getDirectPlayProfiles()) {
if (profile.getType() == DlnaProfileType.Video) {
String container = profile.getContainer();
if (container != null && (StringHelper.IndexOfIgnoreCase(container, "mp4") != -1 ||
StringHelper.IndexOfIgnoreCase(container, "mkv") != -1 ||
StringHelper.IndexOfIgnoreCase(container, "m4v") != -1)) {
if (container != null && (container.toLowerCase().contains(ContainerTypes.MP4) ||
container.toLowerCase().contains(ContainerTypes.MKV) ||
container.toLowerCase().contains(ContainerTypes.M4V))) {
String audioCodec = profile.getAudioCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(audioCodec)) {
profile.setAudioCodec("ac3");
if (Utils.isEmpty(audioCodec)) {
profile.setAudioCodec(CodecTypes.AC3);
} else {
profile.setAudioCodec(audioCodec + ",ac3");
profile.setAudioCodec(audioCodec + "," + CodecTypes.AC3);
}
}
}
@@ -201,16 +209,16 @@ public class AndroidProfile extends DeviceProfile {
for (DirectPlayProfile profile : getDirectPlayProfiles()) {
if (profile.getType() == DlnaProfileType.Video) {
String container = profile.getContainer();
if (container != null && (StringHelper.IndexOfIgnoreCase(container, "mp4") != -1 ||
StringHelper.IndexOfIgnoreCase(container, "mkv") != -1 ||
StringHelper.IndexOfIgnoreCase(container, "m4v") != -1 ||
StringHelper.IndexOfIgnoreCase(container, "ts") != -1)) {
if (container != null && (container.toLowerCase().contains(ContainerTypes.MP4) ||
container.toLowerCase().contains(ContainerTypes.MKV) ||
container.toLowerCase().contains(ContainerTypes.M4V) ||
container.toLowerCase().contains(ContainerTypes.TS))) {
String audioCodec = profile.getAudioCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(audioCodec)) {
profile.setAudioCodec("dca");
if (Utils.isEmpty(audioCodec)) {
profile.setAudioCodec(CodecTypes.DCA);
} else {
profile.setAudioCodec(audioCodec + ",dca");
profile.setAudioCodec(audioCodec + "," + CodecTypes.DCA);
}
}
}
@@ -221,17 +229,18 @@ public class AndroidProfile extends DeviceProfile {
for (DirectPlayProfile profile : getDirectPlayProfiles()) {
if (profile.getType() == DlnaProfileType.Video) {
String container = profile.getContainer();
if (container != null && StringHelper.IndexOfIgnoreCase(container, "mp4") != -1) {
profile.setContainer(container + ",m4v");
if (container != null && container.toLowerCase().contains(ContainerTypes.MP4)) {
profile.setContainer(container + "," + ContainerTypes.M4V);
}
}
}
ArrayList<ResponseProfile> responseProfiles = new ArrayList<>();
responseProfiles.addAll(Arrays.asList(getResponseProfiles()));
ArrayList<ResponseProfile> responseProfiles = new ArrayList<>(
Arrays.asList(getResponseProfiles())
);
ResponseProfile m4vProfile = new ResponseProfile();
m4vProfile.setContainer("m4v");
m4vProfile.setContainer(ContainerTypes.M4V);
m4vProfile.setType(DlnaProfileType.Video);
m4vProfile.setMimeType("video/mp4");
responseProfiles.add(m4vProfile);

View File

@@ -3,6 +3,9 @@ package org.jellyfin.androidtv.model.compat;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import org.jellyfin.androidtv.constants.CodecTypes;
import org.jellyfin.androidtv.constants.ContainerTypes;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.model.dlna.CodecProfile;
import org.jellyfin.apiclient.model.dlna.CodecType;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
@@ -11,7 +14,6 @@ import org.jellyfin.apiclient.model.dlna.DlnaProfileType;
import org.jellyfin.apiclient.model.dlna.ProfileCondition;
import org.jellyfin.apiclient.model.dlna.ProfileConditionType;
import org.jellyfin.apiclient.model.dlna.ProfileConditionValue;
import org.jellyfin.apiclient.model.extensions.StringHelper;
import java.util.ArrayList;
import java.util.Arrays;
@@ -80,9 +82,9 @@ public class Api16Builder {
String[] videoProfiles = GetVideoProfiles(codecCapabilities);
int maxLevel = getMaxLevel(codecCapabilities);
if (StringHelper.EqualsIgnoreCase(profile.getCodec(), "h264")){
if (CodecTypes.H264.equalsIgnoreCase(profile.getCodec())){
if (videoProfiles.length > 0){
conditions.add(new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, tangible.DotNetToJavaStringHelper.join("|", videoProfiles)));
conditions.add(new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, Utils.join("|", videoProfiles)));
}
else{
conditions.add(new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, Defaults.DefaultH264Profile));
@@ -201,13 +203,13 @@ public class Api16Builder {
DirectPlayProfile profile = new DirectPlayProfile();
if (StringHelper.EqualsIgnoreCase(parts[0], "audio")) {
if ("audio".equalsIgnoreCase(parts[0])) {
profile.setType(DlnaProfileType.Audio);
}
else if (StringHelper.EqualsIgnoreCase(parts[0], "video")) {
else if ("video".equalsIgnoreCase(parts[0])) {
profile.setType(DlnaProfileType.Video);
}
else if (StringHelper.EqualsIgnoreCase(parts[0], "image")) {
else if ("image".equalsIgnoreCase(parts[0])) {
profile.setType(DlnaProfileType.Photo);
}
else{
@@ -218,77 +220,73 @@ public class Api16Builder {
// Since we can't get supported codecs per container, we'll have to hardcode them
if (profile.getType()==DlnaProfileType.Video){
profile.setContainer(codecType);
if (StringHelper.IndexOfIgnoreCase(codecType, "mp4") == 0){
profile.setContainer("mp4,m4v");
profile.setVideoCodec("h264,mpeg4");
profile.setAudioCodec("aac");
if (codecType.startsWith(ContainerTypes.MP4)){
profile.setContainer(Utils.join(",", ContainerTypes.MP4, ContainerTypes.M4V));
profile.setVideoCodec(Utils.join(",", CodecTypes.H264, CodecTypes.MPEG4));
profile.setAudioCodec(CodecTypes.AAC);
if (Defaults.SupportsMkv){
profile.setContainer(profile.getContainer() + ",mkv");
profile.setContainer(Utils.join(",", profile.getContainer(), ContainerTypes.MKV));
}
}
else if (StringHelper.EqualsIgnoreCase("avc", codecType)){
profile.setContainer("mp4,m4v");
profile.setVideoCodec("h264,mpeg4");
profile.setAudioCodec("aac");
else if (CodecTypes.AVC.equalsIgnoreCase(codecType)){
profile.setContainer(Utils.join(",", ContainerTypes.MP4, ContainerTypes.M4V));
profile.setVideoCodec(Utils.join(",", CodecTypes.H264, CodecTypes.MPEG4));
profile.setAudioCodec(CodecTypes.AAC);
if (Defaults.SupportsMkv){
profile.setContainer(profile.getContainer() + ",mkv");
profile.setContainer(Utils.join(",", profile.getContainer(), ContainerTypes.MKV));
}
}
else if (StringHelper.EqualsIgnoreCase("hevc", codecType)){
profile.setContainer("mp4,m4v");
profile.setVideoCodec("h265");
profile.setAudioCodec("aac");
else if (CodecTypes.HEVC.equalsIgnoreCase(codecType)){
profile.setContainer(Utils.join(",", ContainerTypes.MP4, ContainerTypes.M4V));
profile.setVideoCodec(CodecTypes.H265);
profile.setAudioCodec(CodecTypes.AAC);
if (Defaults.SupportsMkv){
profile.setContainer(profile.getContainer() + ",mkv");
profile.setContainer(Utils.join(",", profile.getContainer(), ContainerTypes.MKV));
}
}
else if (StringHelper.IndexOfIgnoreCase(codecType, "vp8") != -1){
profile.setContainer("webm");
else if (codecType.contains(CodecTypes.VP8)){
profile.setContainer(ContainerTypes.WEBM);
}
else if (StringHelper.IndexOfIgnoreCase(codecType, "vp9") != -1){
profile.setContainer("webm");
else if (codecType.contains(CodecTypes.VP9)){
profile.setContainer(ContainerTypes.WEBM);
}
else if (StringHelper.EqualsIgnoreCase("3gpp", codecType)){
profile.setContainer("3gp");
else if (ContainerTypes._3GPP.equalsIgnoreCase(codecType)){
profile.setContainer(ContainerTypes._3GP);
}
else if (StringHelper.EqualsIgnoreCase("mpeg2", codecType)){
profile.setContainer("ts,mpegts");
profile.setVideoCodec("mpeg2video,mpeg2");
profile.setAudioCodec("aac");
else if (CodecTypes.MPEG2.equalsIgnoreCase(codecType)){
profile.setContainer(Utils.join(",", ContainerTypes.TS, ContainerTypes.MPEGTS));
profile.setVideoCodec(Utils.join(",", CodecTypes.MPEG2VIDEO, CodecTypes.MPEG2));
profile.setAudioCodec(CodecTypes.AAC);
// If we need to add mp4, m4v or mkv then they must be kept separate from mpegts and in their own DirectPlayProfile
// This is because they have different rules regarding ac3 within the container
}
else {
profile.setContainer(codecType);
}
}
else if (profile.getType()==DlnaProfileType.Audio){
if (StringHelper.IndexOfIgnoreCase(codecType, "mp4") == 0){
profile.setContainer("aac");
if (codecType.startsWith(ContainerTypes.MP4)){
profile.setContainer(CodecTypes.AAC);
}
else if (StringHelper.EqualsIgnoreCase("mpeg", codecType)){
profile.setContainer("mp3");
else if (ContainerTypes.MPEG.equalsIgnoreCase(codecType)){
profile.setContainer(CodecTypes.MP3);
}
else if (StringHelper.EqualsIgnoreCase("3gpp", codecType)){
profile.setContainer("3gp");
else if (ContainerTypes._3GPP.equalsIgnoreCase(codecType)){
profile.setContainer(ContainerTypes._3GP);
}
else if (StringHelper.EqualsIgnoreCase("vorbis", codecType)){
profile.setContainer("webm,webma");
else if (CodecTypes.VORBIS.equalsIgnoreCase(codecType)){
profile.setContainer(Utils.join(",", ContainerTypes.WEBM, ContainerTypes.WEBMA));
}
else if (StringHelper.EqualsIgnoreCase("opus", codecType)){
profile.setContainer("oga,ogg");
else if (CodecTypes.OPUS.equalsIgnoreCase(codecType)){
profile.setContainer(Utils.join(",", ContainerTypes.OGA, ContainerTypes.OGG));
}
else {
// Will cover flac, gsm, and others
profile.setContainer(codecType);
}
@@ -308,10 +306,10 @@ public class Api16Builder {
CodecProfile profile = new CodecProfile();
ArrayList<ProfileCondition> conditions = new ArrayList<ProfileCondition>();
if (StringHelper.EqualsIgnoreCase(parts[0], "audio")) {
if ("audio".equalsIgnoreCase(parts[0])) {
profile.setType(CodecType.Audio);
}
else if (StringHelper.EqualsIgnoreCase(parts[0], "video")) {
else if ("video".equalsIgnoreCase(parts[0])) {
profile.setType(CodecType.Video);
}
else{
@@ -320,42 +318,40 @@ public class Api16Builder {
String codecType = parts[1].toLowerCase();
if (profile.getType()==CodecType.Video){
if (profile.getType()==CodecType.Video) {
conditions.add(new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.Width, "1920"));
conditions.add(new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.Height, "1080"));
conditions.add(new ProfileCondition(ProfileConditionType.NotEquals, ProfileConditionValue.IsAnamorphic, "true"));
if (StringHelper.IndexOfIgnoreCase(codecType, "avc") != -1){
profile.setCodec("h264");
if (codecType.contains(CodecTypes.AVC)) {
profile.setCodec(CodecTypes.H264);
conditions.add(new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.VideoBitDepth, "8"));
}
else if (StringHelper.IndexOfIgnoreCase(codecType, "hevc") != -1){
profile.setCodec("h265");
else if (codecType.contains(CodecTypes.HEVC)) {
profile.setCodec(CodecTypes.H265);
}
else if (StringHelper.IndexOfIgnoreCase(codecType, "vp8") != -1) {
profile.setCodec("vp8");
else if (codecType.contains(CodecTypes.VP8)) {
profile.setCodec(CodecTypes.VP8);
}
else if (StringHelper.IndexOfIgnoreCase(codecType, "vp9") != -1) {
profile.setCodec("vp9");
else if (codecType.contains(CodecTypes.VP9)) {
profile.setCodec(CodecTypes.VP9);
}
else{
profile.setCodec(codecType);
}
}
else if (profile.getType()==CodecType.Audio){
if (StringHelper.IndexOfIgnoreCase(codecType, "mp4") == 0){
profile.setCodec("aac");
if (codecType.startsWith(ContainerTypes.MP4)) {
profile.setCodec(CodecTypes.AAC);
}
else if (StringHelper.EqualsIgnoreCase("mpeg", codecType)){
profile.setCodec("mp3");
else if (ContainerTypes.MPEG.equalsIgnoreCase(codecType)) {
profile.setCodec(CodecTypes.MP3);
conditions.add(new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.AudioBitrate, "320000"));
}
else if (StringHelper.EqualsIgnoreCase("opus", codecType)){
profile.setCodec("vorbis");
else if (CodecTypes.OPUS.equalsIgnoreCase(codecType)) {
profile.setCodec(CodecTypes.VORBIS);
}
else{
profile.setCodec(codecType);
@@ -368,18 +364,15 @@ public class Api16Builder {
newlyAddedCodecProfiles.add(profile);
if (profile.getType()==CodecType.Audio && StringHelper.EqualsIgnoreCase("aac", profile.getCodec())) {
if (profile.getType()==CodecType.Audio && CodecTypes.AAC.equalsIgnoreCase(profile.getCodec())) {
// Create a duplicate under VideoAudioType
CodecProfile videoAudioProfile = new CodecProfile();
videoAudioProfile.setType(CodecType.VideoAudio);
videoAudioProfile.setCodec("aac");
videoAudioProfile.setCodec(CodecTypes.AAC);
ArrayList<ProfileCondition> videoAudioProfileConditions = new ArrayList<ProfileCondition>();
for (ProfileCondition pc : profile.getConditions()){
videoAudioProfileConditions.add(pc);
}
ArrayList<ProfileCondition> videoAudioProfileConditions = new ArrayList<>(
Arrays.asList(profile.getConditions())
);
videoAudioProfile.setConditions(videoAudioProfileConditions.toArray(new ProfileCondition[videoAudioProfileConditions.size()]));
@@ -392,19 +385,17 @@ public class Api16Builder {
private boolean containsDirectPlayProfile(List<DirectPlayProfile> profiles, DirectPlayProfile newProfile){
for (DirectPlayProfile profile : profiles){
if (profile.getType() == newProfile.getType()) {
if (StringHelper.EqualsIgnoreCase(profile.getContainer(), newProfile.getContainer())) {
if (Utils.equalsIgnoreCase(profile.getContainer(), newProfile.getContainer())) {
if (profile.getType() == DlnaProfileType.Audio){
if (StringHelper.EqualsIgnoreCase(profile.getAudioCodec(), newProfile.getAudioCodec())) {
if (Utils.equalsIgnoreCase(profile.getAudioCodec(), newProfile.getAudioCodec())) {
return true;
}
}
else if (profile.getType() == DlnaProfileType.Video){
if (StringHelper.EqualsIgnoreCase(profile.getVideoCodec(), newProfile.getVideoCodec())) {
if (Utils.equalsIgnoreCase(profile.getVideoCodec(), newProfile.getVideoCodec())) {
return true;
}
}
}
}
}
@@ -414,8 +405,7 @@ public class Api16Builder {
private boolean containsCodecProfile(List<CodecProfile> profiles, CodecProfile newProfile){
for (CodecProfile profile : profiles){
if (profile.getType() == newProfile.getType()) {
if (StringHelper.EqualsIgnoreCase(profile.getCodec(), newProfile.getCodec())) {
if (Utils.equalsIgnoreCase(profile.getCodec(), newProfile.getCodec())) {
return true;
}
}

View File

@@ -3,12 +3,12 @@ package org.jellyfin.androidtv.model.compat;
import android.media.MediaCodecInfo;
import android.util.Range;
import org.jellyfin.androidtv.constants.CodecTypes;
import org.jellyfin.apiclient.model.dlna.CodecProfile;
import org.jellyfin.apiclient.model.dlna.CodecType;
import org.jellyfin.apiclient.model.dlna.ProfileCondition;
import org.jellyfin.apiclient.model.dlna.ProfileConditionType;
import org.jellyfin.apiclient.model.dlna.ProfileConditionValue;
import org.jellyfin.apiclient.model.extensions.StringHelper;
import java.util.ArrayList;
@@ -38,8 +38,7 @@ public class Api21Builder extends Api16Builder {
conditions.add(new ProfileCondition(ProfileConditionType.NotEquals, ProfileConditionValue.IsAnamorphic, "true"));
if (StringHelper.IndexOfIgnoreCase(profile.getCodec(), "h264") != -1) {
if (profile.getCodec() != null && profile.getCodec().toLowerCase().contains(CodecTypes.H264)) {
conditions.add(new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.VideoBitDepth, "8"));
}

View File

@@ -2,6 +2,9 @@ package org.jellyfin.androidtv.model.compat;
import java.util.Arrays;
import org.jellyfin.androidtv.constants.CodecTypes;
import org.jellyfin.androidtv.constants.MediaTypes;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.model.dlna.CodecProfile;
import org.jellyfin.apiclient.model.dlna.CodecType;
import org.jellyfin.apiclient.model.dlna.ConditionProcessor;
@@ -17,10 +20,7 @@ import org.jellyfin.apiclient.model.dlna.TranscodingProfile;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.entities.MediaStream;
import org.jellyfin.apiclient.model.entities.MediaStreamType;
import org.jellyfin.apiclient.model.extensions.FloatHelper;
import org.jellyfin.apiclient.model.extensions.IntHelper;
import org.jellyfin.apiclient.model.extensions.ListHelper;
import org.jellyfin.apiclient.model.extensions.StringHelper;
import org.jellyfin.apiclient.model.logging.ILogger;
import org.jellyfin.apiclient.model.mediainfo.MediaProtocol;
import org.jellyfin.apiclient.model.mediainfo.TransportStreamTimestamp;
@@ -50,7 +50,7 @@ public class StreamBuilder
java.util.ArrayList<MediaSourceInfo> mediaSources = new java.util.ArrayList<MediaSourceInfo>();
for (MediaSourceInfo i : options.getMediaSources())
{
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(options.getMediaSourceId()) || StringHelper.EqualsIgnoreCase(i.getId(), options.getMediaSourceId()))
if (Utils.isEmpty(options.getMediaSourceId()) || Utils.equalsIgnoreCase(i.getId(), options.getMediaSourceId()))
{
mediaSources.add(i);
}
@@ -82,7 +82,7 @@ public class StreamBuilder
java.util.ArrayList<MediaSourceInfo> mediaSources = new java.util.ArrayList<MediaSourceInfo>();
for (MediaSourceInfo i : options.getMediaSources())
{
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(options.getMediaSourceId()) || StringHelper.EqualsIgnoreCase(i.getId(), options.getMediaSourceId()))
if (Utils.isEmpty(options.getMediaSourceId()) || Utils.equalsIgnoreCase(i.getId(), options.getMediaSourceId()))
{
mediaSources.add(i);
}
@@ -158,7 +158,7 @@ public class StreamBuilder
String audioCodec = audioStream == null ? null : audioStream.getCodec();
// Make sure audio codec profiles are satisfied
if (!tangible.DotNetToJavaStringHelper.isNullOrEmpty(audioCodec))
if (!Utils.isEmpty(audioCodec))
{
java.util.ArrayList<ProfileCondition> conditions = new java.util.ArrayList<ProfileCondition>();
for (CodecProfile i : options.getProfile().getCodecProfiles())
@@ -237,7 +237,7 @@ public class StreamBuilder
playlistItem.setEstimateContentLength(transcodingProfile.getEstimateContentLength());
playlistItem.setContainer(transcodingProfile.getContainer());
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(transcodingProfile.getAudioCodec()))
if (Utils.isEmpty(transcodingProfile.getAudioCodec()))
{
playlistItem.setAudioCodecs(new String[] { });
}
@@ -391,7 +391,7 @@ public class StreamBuilder
{
for (SubtitleProfile profile : subtitleProfiles)
{
if (profile.getMethod() == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.getFormat(), stream.getCodec()))
if (profile.getMethod() == SubtitleDeliveryMethod.External && Utils.equalsIgnoreCase(profile.getFormat(), stream.getCodec()))
{
return stream.getIndex();
}
@@ -495,16 +495,12 @@ public class StreamBuilder
playlistItem.setCopyTimestamps(transcodingProfile.getCopyTimestamps());
playlistItem.setEnableSubtitlesInManifest(transcodingProfile.getEnableSubtitlesInManifest());
if (!tangible.DotNetToJavaStringHelper.isNullOrEmpty(transcodingProfile.getMaxAudioChannels()))
if (!Utils.isEmpty(transcodingProfile.getMaxAudioChannels()))
{
int transcodingMaxAudioChannels = 0;
tangible.RefObject<Integer> tempRef_transcodingMaxAudioChannels = new tangible.RefObject<Integer>(transcodingMaxAudioChannels);
boolean tempVar6 = IntHelper.TryParseCultureInvariant(transcodingProfile.getMaxAudioChannels(), tempRef_transcodingMaxAudioChannels);
transcodingMaxAudioChannels = tempRef_transcodingMaxAudioChannels.argValue;
if (tempVar6)
{
try {
int transcodingMaxAudioChannels = Integer.parseInt(transcodingProfile.getMaxAudioChannels());
playlistItem.setTranscodingMaxAudioChannels(transcodingMaxAudioChannels);
}
} catch (NumberFormatException ignored) {}
}
playlistItem.setSubProtocol(transcodingProfile.getProtocol());
playlistItem.setAudioStreamIndex(audioStreamIndex);
@@ -631,10 +627,10 @@ public class StreamBuilder
// Reduce the bitrate if we're downmixing
if (targetAudioChannels != null && audioStream != null && audioStream.getChannels() != null && targetAudioChannels < audioStream.getChannels())
{
defaultBitrate = StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3") ? 192000 : 128000;
defaultBitrate = CodecTypes.AC3.equalsIgnoreCase(targetAudioCodec) ? 192000 : 128000;
}
if (StringHelper.EqualsIgnoreCase(subProtocol, "hls"))
if (MediaTypes.HLS.equalsIgnoreCase(subProtocol))
{
defaultBitrate = Math.min(384000, defaultBitrate);
}
@@ -764,7 +760,7 @@ public class StreamBuilder
String videoCodec = videoStream == null ? null : videoStream.getCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(videoCodec))
if (Utils.isEmpty(videoCodec))
{
String tempVar6 = profile.getName();
String tempVar7 = mediaSource.getPath();
@@ -813,7 +809,7 @@ public class StreamBuilder
{
String audioCodec = audioStream.getCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(audioCodec))
if (Utils.isEmpty(audioCodec))
{
String tempVar8 = profile.getName();
String tempVar9 = mediaSource.getPath();
@@ -910,7 +906,7 @@ public class StreamBuilder
continue;
}
if (subtitleStream.getIsTextSubtitleStream() == MediaStream.IsTextFormat(profile.getFormat()) && StringHelper.EqualsIgnoreCase(profile.getFormat(), subtitleStream.getCodec()))
if (subtitleStream.getIsTextSubtitleStream() == MediaStream.IsTextFormat(profile.getFormat()) && Utils.equalsIgnoreCase(profile.getFormat(), subtitleStream.getCodec()))
{
return profile;
}
@@ -947,7 +943,7 @@ public class StreamBuilder
if ((profile.getMethod() == SubtitleDeliveryMethod.External && subtitleStream.getIsTextSubtitleStream() == MediaStream.IsTextFormat(profile.getFormat())) || (profile.getMethod() == SubtitleDeliveryMethod.Hls && subtitleStream.getIsTextSubtitleStream()))
{
boolean requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.getCodec(), profile.getFormat());
boolean requiresConversion = !Utils.equalsIgnoreCase(subtitleStream.getCodec(), profile.getFormat());
if (!requiresConversion)
{
@@ -996,12 +992,12 @@ public class StreamBuilder
{
ValidateAudioInput(options);
if (options.getAudioStreamIndex() != null && tangible.DotNetToJavaStringHelper.isNullOrEmpty(options.getMediaSourceId()))
if (options.getAudioStreamIndex() != null && Utils.isEmpty(options.getMediaSourceId()))
{
throw new IllegalArgumentException("MediaSourceId is required when a specific audio stream is requested");
}
if (options.getSubtitleStreamIndex() != null && tangible.DotNetToJavaStringHelper.isNullOrEmpty(options.getMediaSourceId()))
if (options.getSubtitleStreamIndex() != null && Utils.isEmpty(options.getMediaSourceId()))
{
throw new IllegalArgumentException("MediaSourceId is required when a specific subtitle stream is requested");
}
@@ -1009,11 +1005,11 @@ public class StreamBuilder
private void ValidateAudioInput(AudioOptions options)
{
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(options.getItemId()))
if (Utils.isEmpty(options.getItemId()))
{
throw new IllegalArgumentException("ItemId is required");
}
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(options.getDeviceId()))
if (Utils.isEmpty(options.getDeviceId()))
{
throw new IllegalArgumentException("DeviceId is required");
}
@@ -1033,7 +1029,7 @@ public class StreamBuilder
{
String value = condition.getValue();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(value))
if (Utils.isEmpty(value))
{
continue;
}
@@ -1048,26 +1044,18 @@ public class StreamBuilder
{
case AudioBitrate:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num = new tangible.RefObject<Integer>(num);
boolean tempVar = IntHelper.TryParseCultureInvariant(value, tempRef_num);
num = tempRef_num.argValue;
if (tempVar)
{
try {
int num = Integer.parseInt(value);
item.setAudioBitrate(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case AudioChannels:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num2 = new tangible.RefObject<Integer>(num);
boolean tempVar2 = IntHelper.TryParseCultureInvariant(value, tempRef_num2);
num = tempRef_num2.argValue;
if (tempVar2)
{
try {
int num = Integer.parseInt(value);
item.setMaxAudioChannels(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case IsAnamorphic:
@@ -1084,26 +1072,18 @@ public class StreamBuilder
}
case RefFrames:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num3 = new tangible.RefObject<Integer>(num);
boolean tempVar3 = IntHelper.TryParseCultureInvariant(value, tempRef_num3);
num = tempRef_num3.argValue;
if (tempVar3)
{
try {
int num = Integer.parseInt(value);
item.setMaxRefFrames(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case VideoBitDepth:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num4 = new tangible.RefObject<Integer>(num);
boolean tempVar4 = IntHelper.TryParseCultureInvariant(value, tempRef_num4);
num = tempRef_num4.argValue;
if (tempVar4)
{
try {
int num = Integer.parseInt(value);
item.setMaxVideoBitDepth(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case VideoProfile:
@@ -1113,62 +1093,42 @@ public class StreamBuilder
}
case Height:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num5 = new tangible.RefObject<Integer>(num);
boolean tempVar5 = IntHelper.TryParseCultureInvariant(value, tempRef_num5);
num = tempRef_num5.argValue;
if (tempVar5)
{
try {
int num = Integer.parseInt(value);
item.setMaxHeight(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case VideoBitrate:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num6 = new tangible.RefObject<Integer>(num);
boolean tempVar6 = IntHelper.TryParseCultureInvariant(value, tempRef_num6);
num = tempRef_num6.argValue;
if (tempVar6)
{
try {
int num = Integer.parseInt(value);
item.setVideoBitrate(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case VideoFramerate:
{
float num = 0F;
tangible.RefObject<Float> tempRef_num7 = new tangible.RefObject<Float>(num);
boolean tempVar7 = FloatHelper.TryParseCultureInvariant(value, tempRef_num7);
num = tempRef_num7.argValue;
if (tempVar7)
{
try {
float num = Float.parseFloat(value);
item.setMaxFramerate(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case VideoLevel:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num8 = new tangible.RefObject<Integer>(num);
boolean tempVar8 = IntHelper.TryParseCultureInvariant(value, tempRef_num8);
num = tempRef_num8.argValue;
if (tempVar8)
{
try {
int num = Integer.parseInt(value);
item.setVideoLevel(num);
}
} catch (NumberFormatException ignored) {}
break;
}
case Width:
{
int num = 0;
tangible.RefObject<Integer> tempRef_num9 = new tangible.RefObject<Integer>(num);
boolean tempVar9 = IntHelper.TryParseCultureInvariant(value, tempRef_num9);
num = tempRef_num9.argValue;
if (tempVar9)
{
try {
int num = Integer.parseInt(value);
item.setMaxWidth(num);
}
} catch (NumberFormatException ignored) {}
break;
}
}
@@ -1185,7 +1145,7 @@ public class StreamBuilder
boolean any = false;
for (String i : profile.GetContainers())
{
if (StringHelper.EqualsIgnoreCase(i, mediaContainer))
if (Utils.equalsIgnoreCase(i, mediaContainer))
{
any = true;
break;
@@ -1203,7 +1163,7 @@ public class StreamBuilder
{
// Check audio codecs
String audioCodec = audioStream == null ? null : audioStream.getCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(audioCodec) || !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
if (Utils.isEmpty(audioCodec) || !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
{
return false;
}
@@ -1222,7 +1182,7 @@ public class StreamBuilder
boolean any = false;
for (String i : profile.GetContainers())
{
if (StringHelper.EqualsIgnoreCase(i, mediaContainer))
if (Utils.equalsIgnoreCase(i, mediaContainer))
{
any = true;
break;
@@ -1239,7 +1199,7 @@ public class StreamBuilder
if (videoCodecs.size() > 0)
{
String videoCodec = videoStream == null ? null : videoStream.getCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(videoCodec) || !ListHelper.ContainsIgnoreCase(videoCodecs, videoCodec))
if (Utils.isEmpty(videoCodec) || !ListHelper.ContainsIgnoreCase(videoCodecs, videoCodec))
{
return false;
}
@@ -1251,7 +1211,7 @@ public class StreamBuilder
{
// Check audio codecs
String audioCodec = audioStream == null ? null : audioStream.getCodec();
if (tangible.DotNetToJavaStringHelper.isNullOrEmpty(audioCodec) || !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
if (Utils.isEmpty(audioCodec) || !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
{
return false;
}

View File

@@ -1,5 +1,7 @@
package org.jellyfin.androidtv.model.compat;
import org.jellyfin.androidtv.constants.ContainerTypes;
import org.jellyfin.androidtv.constants.MediaTypes;
import org.jellyfin.androidtv.util.Utils;
import java.util.ArrayList;
@@ -14,7 +16,6 @@ import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
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.extensions.StringHelper;
import org.jellyfin.apiclient.model.mediainfo.MediaProtocol;
import org.jellyfin.apiclient.model.mediainfo.TransportStreamTimestamp;
import org.jellyfin.apiclient.model.session.PlayMethod;
@@ -26,6 +27,10 @@ import org.jellyfin.apiclient.model.session.PlayMethod;
*/
@Deprecated
public class StreamInfo {
private static final String START_TIME_TICKS = "StartTimeTicks";
private static final String SUBTITLE_STREAM_INDEX = "SubtitleStreamIndex";
private static final String STATIC = "Static";
public StreamInfo() {
setAudioCodecs(new String[]{});
}
@@ -428,13 +433,13 @@ public class StreamInfo {
}
// Try to keep the url clean by omitting defaults
if (StringHelper.EqualsIgnoreCase(pair.getName(), "StartTimeTicks") && StringHelper.EqualsIgnoreCase(pair.getValue(), "0")) {
if (START_TIME_TICKS.equalsIgnoreCase(pair.getName()) && "0".equalsIgnoreCase(pair.getValue())) {
continue;
}
if (StringHelper.EqualsIgnoreCase(pair.getName(), "SubtitleStreamIndex") && StringHelper.EqualsIgnoreCase(pair.getValue(), "-1")) {
if (SUBTITLE_STREAM_INDEX.equalsIgnoreCase(pair.getName()) && "-1".equalsIgnoreCase(pair.getValue())) {
continue;
}
if (StringHelper.EqualsIgnoreCase(pair.getName(), "Static") && StringHelper.EqualsIgnoreCase(pair.getValue(), "false")) {
if (STATIC.equalsIgnoreCase(pair.getName()) && "false".equalsIgnoreCase(pair.getValue())) {
continue;
}
@@ -466,14 +471,14 @@ public class StreamInfo {
baseUrl = baseUrl.replaceAll("[/]+$", "");
if (getMediaType() == DlnaProfileType.Audio) {
if (StringHelper.EqualsIgnoreCase(getSubProtocol(), "hls")) {
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 (StringHelper.EqualsIgnoreCase(getSubProtocol(), "hls")) {
if (MediaTypes.HLS.equalsIgnoreCase(getSubProtocol())) {
return String.format("%1$s/videos/%2$s/master.m3u8?%3$s", baseUrl, getItemId(), queryString);
}
@@ -501,29 +506,29 @@ public class StreamInfo {
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", (new Boolean(item.getIsDirectStream())).toString().toLowerCase()));
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("AudioCodec", audioCodecs));
list.add(new NameValuePair("AudioStreamIndex", item.getAudioStreamIndex() != null ? StringHelper.ToStringCultureInvariant(item.getAudioStreamIndex()) : ""));
list.add(new NameValuePair("SubtitleStreamIndex", item.getSubtitleStreamIndex() != null && item.getSubtitleDeliveryMethod() != org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.getSubtitleStreamIndex()) : ""));
list.add(new NameValuePair("VideoBitrate", item.getVideoBitrate() != null ? StringHelper.ToStringCultureInvariant(item.getVideoBitrate()) : ""));
list.add(new NameValuePair("AudioBitrate", item.getAudioBitrate() != null ? StringHelper.ToStringCultureInvariant(item.getAudioBitrate()) : ""));
list.add(new NameValuePair("MaxAudioChannels", item.getMaxAudioChannels() != null ? StringHelper.ToStringCultureInvariant(item.getMaxAudioChannels()) : ""));
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 ? StringHelper.ToStringCultureInvariant(item.getMaxWidth()) : ""));
list.add(new NameValuePair("MaxHeight", item.getMaxHeight() != null ? StringHelper.ToStringCultureInvariant(item.getMaxHeight()) : ""));
list.add(new NameValuePair("MaxWidth", item.getMaxWidth() != null ? String.valueOf(item.getMaxWidth()) : ""));
list.add(new NameValuePair("MaxHeight", item.getMaxHeight() != null ? String.valueOf(item.getMaxHeight()) : ""));
if (StringHelper.EqualsIgnoreCase(item.getSubProtocol(), "hls") && !item.getForceLiveStream()) {
list.add(new NameValuePair("StartTimeTicks", ""));
if (MediaTypes.HLS.equalsIgnoreCase(item.getSubProtocol()) && !item.getForceLiveStream()) {
list.add(new NameValuePair(START_TIME_TICKS, ""));
} else {
list.add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.getStartPositionTicks())));
list.add(new NameValuePair(START_TIME_TICKS, String.valueOf(item.getStartPositionTicks())));
}
list.add(new NameValuePair("Level", item.getVideoLevel() != null ? StringHelper.ToStringCultureInvariant(item.getVideoLevel()) : ""));
list.add(new NameValuePair("Level", item.getVideoLevel() != null ? String.valueOf(item.getVideoLevel()) : ""));
list.add(new NameValuePair("MaxRefFrames", item.getMaxRefFrames() != null ? StringHelper.ToStringCultureInvariant(item.getMaxRefFrames()) : ""));
list.add(new NameValuePair("MaxVideoBitDepth", item.getMaxVideoBitDepth() != null ? StringHelper.ToStringCultureInvariant(item.getMaxVideoBitDepth()) : ""));
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 : ""));
@@ -541,12 +546,12 @@ public class StreamInfo {
list.add(new NameValuePair("ItemId", item.getItemId()));
}
list.add(new NameValuePair("CopyTimestamps", (new Boolean(item.getCopyTimestamps())).toString().toLowerCase()));
list.add(new NameValuePair("ForceLiveStream", (new Boolean(item.getForceLiveStream())).toString().toLowerCase()));
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("TranscodingMaxAudioChannels", item.getTranscodingMaxAudioChannels() != null ? StringHelper.ToStringCultureInvariant(item.getTranscodingMaxAudioChannels()) : ""));
list.add(new NameValuePair("EnableSubtitlesInManifest", (new Boolean(item.getEnableSubtitlesInManifest())).toString().toLowerCase()));
list.add(new NameValuePair("TranscodingMaxAudioChannels", item.getTranscodingMaxAudioChannels() != null ? String.valueOf(item.getTranscodingMaxAudioChannels()) : ""));
list.add(new NameValuePair("EnableSubtitlesInManifest", Boolean.valueOf(item.getEnableSubtitlesInManifest()).toString().toLowerCase()));
String tempVar7 = item.getMediaSource().getETag();
list.add(new NameValuePair("Tag", (tempVar7 != null) ? tempVar7 : ""));
@@ -580,7 +585,7 @@ public class StreamInfo {
ArrayList<SubtitleStreamInfo> list = new ArrayList<SubtitleStreamInfo>();
// HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = StringHelper.EqualsIgnoreCase(getSubProtocol(), "hls") ? 0 : (getPlayMethod() == PlayMethod.Transcode && !getCopyTimestamps() ? getStartPositionTicks() : 0);
long startPositionTicks = MediaTypes.HLS.equalsIgnoreCase(getSubProtocol()) ? 0 : (getPlayMethod() == PlayMethod.Transcode && !getCopyTimestamps() ? getStartPositionTicks() : 0);
// First add the selected track
if (getSubtitleStreamIndex() != null) {
@@ -630,8 +635,8 @@ public class StreamInfo {
SubtitleStreamInfo info = tempVar;
if (info.getDeliveryMethod() == SubtitleDeliveryMethod.External) {
if (getMediaSource().getProtocol() == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.getCodec(), subtitleProfile.getFormat())) {
info.setUrl(String.format("%1$s/Videos/%2$s/%3$s/Subtitles/%4$s/%5$s/Stream.%6$s", baseUrl, getItemId(), getMediaSourceId(), StringHelper.ToStringCultureInvariant(stream.getIndex()), StringHelper.ToStringCultureInvariant(startPositionTicks), subtitleProfile.getFormat()));
if (getMediaSource().getProtocol() == MediaProtocol.File || !stream.getCodec().equalsIgnoreCase(subtitleProfile.getFormat())) {
info.setUrl(String.format("%1$s/Videos/%2$s/%3$s/Subtitles/%4$s/%5$s/Stream.%6$s", baseUrl, getItemId(), getMediaSourceId(), String.valueOf(stream.getIndex()), String.valueOf(startPositionTicks), subtitleProfile.getFormat()));
if (!Utils.isEmpty(accessToken)) {
info.setUrl(info.getUrl() + "?api_key=" + accessToken);
@@ -777,7 +782,7 @@ public class StreamInfo {
}
for (String codec : getAudioCodecs()) {
if (StringHelper.EqualsIgnoreCase(codec, inputCodec)) {
if (codec.equalsIgnoreCase(inputCodec)) {
return codec;
}
}
@@ -815,8 +820,7 @@ public class StreamInfo {
}
public final TransportStreamTimestamp getTargetTimestamp() {
TransportStreamTimestamp defaultValue = StringHelper.EqualsIgnoreCase(getContainer(), "m2ts") ? TransportStreamTimestamp.Valid : TransportStreamTimestamp.None;
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;
}

View File

@@ -9,4 +9,5 @@ public class AudioEventListener {
public void onPlaybackStateChange(PlaybackController.PlaybackState newState, BaseItemDto currentItem) {}
public void onProgress(long pos) {}
public void onQueueStatusChanged(boolean hasQueue) {}
public void onQueueReplaced(){}
}

View File

@@ -8,16 +8,16 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.app.BackgroundManager;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.View;
@@ -83,13 +83,12 @@ public class AudioNowPlayingActivity extends BaseActivity {
private TextView mSongTitle;
private TextView mAlbumTitle;
private TextView mCurrentNdx;
private TextView mTotal;
private ImageView mPoster;
private ProgressBar mCurrentProgress;
private TextView mCurrentPos;
private TextView mRemainingTime;
private int mCurrentDuration;
private RowsFragment mRowsFragment;
private RowsSupportFragment mRowsFragment;
private ArrayObjectAdapter mRowsAdapter;
private static PositionableListRowPresenter mAudioQueuePresenter;
@@ -119,36 +118,37 @@ public class AudioNowPlayingActivity extends BaseActivity {
mActivity = this;
roboto = mApplication.getDefaultFont();
mClock = (ClockUserView) findViewById(R.id.clock);
mPoster = (ImageView) findViewById(R.id.poster);
mArtistName = (TextView) findViewById(R.id.artistTitle);
mClock = findViewById(R.id.clock);
mPoster = findViewById(R.id.poster);
mArtistName = findViewById(R.id.artistTitle);
mArtistName.setTypeface(roboto);
mGenreRow = (LinearLayout) findViewById(R.id.genreRow);
mSongTitle = (TextView) findViewById(R.id.songTitle);
mGenreRow = findViewById(R.id.genreRow);
mSongTitle = findViewById(R.id.song);
mSongTitle.setTypeface(roboto);
mAlbumTitle = (TextView) findViewById(R.id.albumTitle);
mAlbumTitle = findViewById(R.id.album);
mAlbumTitle.setTypeface(roboto);
mCurrentNdx = (TextView) findViewById(R.id.currentNdx);
mScrollView = (ScrollView) findViewById(R.id.mainScroller);
mCounter = (TextView) findViewById(R.id.counter);
mCurrentNdx = findViewById(R.id.track);
mCurrentNdx.setTypeface(roboto);
mScrollView = findViewById(R.id.mainScroller);
mCounter = findViewById(R.id.counter);
mCounter.setTypeface(roboto);
mLogoImage = (ImageView) findViewById(R.id.artistLogo);
mLogoImage = findViewById(R.id.artistLogo);
mSSArea = (RelativeLayout) findViewById(R.id.ssInfoArea);
mSSTime = (TextView) findViewById(R.id.ssTime);
mSSArea = findViewById(R.id.ssInfoArea);
mSSTime = findViewById(R.id.ssTime);
mSSTime.setTypeface(roboto);
mSSAlbumSong = (TextView) findViewById(R.id.ssAlbumSong);
mSSAlbumSong = findViewById(R.id.ssAlbumSong);
mSSAlbumSong.setTypeface(roboto);
mSSQueueStatus = (TextView) findViewById(R.id.ssQueueStatus);
mSSQueueStatus = findViewById(R.id.ssQueueStatus);
mSSQueueStatus.setTypeface(roboto);
mSSUpNext = (TextView) findViewById(R.id.ssUpNext);
mSSUpNext = findViewById(R.id.ssUpNext);
mSSUpNext.setTypeface(roboto);
mPlayPauseButton = (ImageButton) findViewById(R.id.playPauseBtn);
mPlayPauseButton.setSecondaryImage(R.drawable.lb_ic_pause);
mPlayPauseButton.setPrimaryImage(R.drawable.play);
TextView helpView = (TextView) findViewById(R.id.buttonTip);
mPrevButton = (ImageButton) findViewById(R.id.prevBtn);
mPlayPauseButton = findViewById(R.id.playPauseBtn);
mPlayPauseButton.setSecondaryImage(R.drawable.ic_pause);
mPlayPauseButton.setPrimaryImage(R.drawable.ic_play);
TextView helpView = findViewById(R.id.buttonTip);
mPrevButton = findViewById(R.id.prevBtn);
mPrevButton.setHelpView(helpView);
mPrevButton.setHelpText(getString(R.string.lbl_prev_item));
mPrevButton.setOnClickListener(new View.OnClickListener() {
@@ -158,7 +158,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
}
});
mPrevButton.setGotFocusListener(mainAreaFocusListener);
mNextButton = (ImageButton) findViewById(R.id.nextBtn);
mNextButton = findViewById(R.id.nextBtn);
mNextButton.setHelpView(helpView);
mNextButton.setHelpText(getString(R.string.lbl_next_item));
mNextButton.setOnClickListener(new View.OnClickListener() {
@@ -168,11 +168,11 @@ public class AudioNowPlayingActivity extends BaseActivity {
}
});
mNextButton.setGotFocusListener(mainAreaFocusListener);
mRepeatButton = (ImageButton) findViewById(R.id.repeatBtn);
mRepeatButton = findViewById(R.id.repeatBtn);
mRepeatButton.setHelpView(helpView);
mRepeatButton.setHelpText(getString(R.string.lbl_toggle_repeat));
mRepeatButton.setPrimaryImage(R.drawable.loop);
mRepeatButton.setSecondaryImage(R.drawable.loopred);
mRepeatButton.setHelpText(getString(R.string.lbl_repeat));
mRepeatButton.setPrimaryImage(R.drawable.ic_loop);
mRepeatButton.setSecondaryImage(R.drawable.ic_loop_red);
mRepeatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -180,7 +180,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
updateButtons(MediaManager.isPlayingAudio());
}
});
mSaveButton = (ImageButton) findViewById(R.id.saveBtn);
mSaveButton = findViewById(R.id.saveBtn);
mSaveButton.setHelpView(helpView);
mSaveButton.setHelpText(getString(R.string.lbl_save_as_playlist));
mSaveButton.setOnClickListener(new View.OnClickListener() {
@@ -190,7 +190,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
}
});
mRepeatButton.setGotFocusListener(mainAreaFocusListener);
mShuffleButton = (ImageButton) findViewById(R.id.shuffleBtn);
mShuffleButton = findViewById(R.id.shuffleBtn);
mShuffleButton.setHelpView(helpView);
mShuffleButton.setHelpText(getString(R.string.lbl_reshuffle_queue));
mShuffleButton.setOnClickListener(new View.OnClickListener() {
@@ -203,8 +203,6 @@ public class AudioNowPlayingActivity extends BaseActivity {
@Override
public void onClick(DialogInterface dialog, int which) {
MediaManager.shuffleAudioQueue();
mRowsAdapter.remove(mQueueRow);
addQueue();
}
})
.setNegativeButton(mActivity.getString(R.string.lbl_no), null)
@@ -212,7 +210,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
}
});
mShuffleButton.setGotFocusListener(mainAreaFocusListener);
mAlbumButton = (ImageButton) findViewById(R.id.albumBtn);
mAlbumButton = findViewById(R.id.albumBtn);
mAlbumButton.setHelpView(helpView);
mAlbumButton.setHelpText(getString(R.string.lbl_open_album));
mAlbumButton.setOnClickListener(new View.OnClickListener() {
@@ -224,7 +222,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
}
});
mAlbumButton.setGotFocusListener(mainAreaFocusListener);
mArtistButton = (ImageButton) findViewById(R.id.artistBtn);
mArtistButton = findViewById(R.id.artistBtn);
mArtistButton.setHelpView(helpView);
mArtistButton.setHelpText(getString(R.string.lbl_open_artist));
mArtistButton.setOnClickListener(new View.OnClickListener() {
@@ -240,10 +238,9 @@ public class AudioNowPlayingActivity extends BaseActivity {
});
mArtistButton.setGotFocusListener(mainAreaFocusListener);
mCurrentProgress = (ProgressBar) findViewById(R.id.playerProgress);
mCurrentPos = (TextView) findViewById(R.id.currentPos);
mRemainingTime = (TextView) findViewById(R.id.remainingTime);
mTotal = (TextView) findViewById(R.id.total);
mCurrentProgress = findViewById(R.id.playerProgress);
mCurrentPos = findViewById(R.id.currentPos);
mRemainingTime = findViewById(R.id.remainingTime);
mPlayPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -255,13 +252,12 @@ public class AudioNowPlayingActivity extends BaseActivity {
BackgroundManager backgroundManager = BackgroundManager.getInstance(this);
backgroundManager.attach(getWindow());
backgroundManager.setDimLayer(getDrawable(R.drawable.left_fade));
mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
mMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
mRowsFragment = new RowsFragment();
getFragmentManager().beginTransaction().add(R.id.rowsFragment, mRowsFragment).commit();
mRowsFragment = new RowsSupportFragment();
getSupportFragmentManager().beginTransaction().add(R.id.rowsFragment, mRowsFragment).commit();
//create list background gradient
mListBackground = mApplication.getCurrentBackgroundGradient();
@@ -343,8 +339,6 @@ public class AudioNowPlayingActivity extends BaseActivity {
MediaManager.prevAudioItem();
return true;
}
break;
}
if (ssActive) {
@@ -385,6 +379,12 @@ public class AudioNowPlayingActivity extends BaseActivity {
finish(); // entire queue removed nothing to do here
}
}
@Override
public void onQueueReplaced() {
mRowsAdapter.remove(mQueueRow);
addQueue();
}
};
private GotFocusEvent mainAreaFocusListener = new GotFocusEvent() {
@@ -410,7 +410,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
Picasso.with(mActivity)
.load(primaryImageUrl)
.skipMemoryCache()
.error(R.drawable.audioicon)
.error(R.drawable.ic_album)
.resize(posterWidth, posterHeight)
.centerInside()
.into(mPoster);
@@ -460,16 +460,16 @@ public class AudioNowPlayingActivity extends BaseActivity {
if (item != null) {
mArtistName.setText(getArtistName(item));
mSongTitle.setText(item.getName());
mAlbumTitle.setText(item.getAlbum());
mCurrentNdx.setText(MediaManager.getCurrentAudioQueueDisplayPosition());
mTotal.setText(MediaManager.getCurrentAudioQueueDisplaySize());
mAlbumTitle.setText(String.format(getResources().getString(R.string.lbl_now_playing_album), item.getAlbum()));
mCurrentNdx.setText(String.format(getResources().getString(R.string.lbl_now_playing_track),
MediaManager.getCurrentAudioQueueDisplayPosition(),
MediaManager.getCurrentAudioQueueDisplaySize()));
mCurrentDuration = ((Long)((item.getRunTimeTicks() != null ? item.getRunTimeTicks() : 0) / 10000)).intValue();
//set progress to match duration
mCurrentProgress.setMax(mCurrentDuration);
addGenres(mGenreRow);
updateBackground(ImageUtils.getBackdropImageUrl(item, TvApp.getApplication().getApiClient(), true));
}
}
public void setCurrentTime(long time) {
@@ -489,7 +489,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
for (String genre : mBaseItem.getGenres()) {
if (!first) InfoLayoutHelper.addSpacer(this, layout, " / ", 14);
first = false;
layout.addView(new GenreButton(this, roboto, 16, genre, mBaseItem.getType()));
layout.addView(new GenreButton(this, roboto, 16, genre, mBaseItem.getBaseItemType()));
}
}
}
@@ -601,5 +601,4 @@ public class AudioNowPlayingActivity extends BaseActivity {
.into(mBackgroundTarget);
}
}
}

View File

@@ -1,7 +1,6 @@
package org.jellyfin.androidtv.playback;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -12,17 +11,11 @@ import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -50,7 +43,6 @@ import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.base.CustomMessage;
import org.jellyfin.androidtv.base.IMessageListener;
import org.jellyfin.androidtv.integration.RecommendationManager;
import org.jellyfin.androidtv.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.livetv.ILiveTvGuide;
@@ -80,14 +72,6 @@ import org.jellyfin.androidtv.util.TimeUtils;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.BaseItemUtils;
import org.jellyfin.androidtv.util.apiclient.StreamHelper;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
@@ -100,6 +84,24 @@ import org.jellyfin.apiclient.model.livetv.SeriesTimerInfoDto;
import org.jellyfin.apiclient.model.mediainfo.SubtitleTrackEvent;
import org.jellyfin.apiclient.model.mediainfo.SubtitleTrackInfo;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
public class CustomPlaybackOverlayFragment extends Fragment implements IPlaybackOverlayFragment, ILiveTvGuide {
ImageView mPoster;
ImageView mStudioImage;
@@ -115,7 +117,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
LinearLayout mInfoRow;
LinearLayout mButtonRow;
FrameLayout mPopupArea;
RowsFragment mPopupRowsFragment;
RowsSupportFragment mPopupRowsFragment;
ArrayObjectAdapter mPopupRowAdapter;
ListRow mChapterRow;
PositionableListRowPresenter mPopupRowPresenter;
@@ -204,11 +206,10 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mApplication = TvApp.getApplication();
mActivity = (PlaybackOverlayActivity) getActivity();
//stop any audio that may be playing
// stop any audio that may be playing
MediaManager.stopAudio();
mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) {
mApplication.getLogger().Error("Unable to get audio manager");
Utils.showToast(getActivity(), R.string.msg_cannot_play_time);
@@ -219,7 +220,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mActivity.setKeyListener(keyListener);
mItemsToPlay = MediaManager.getCurrentVideoQueue();
if (mItemsToPlay == null || mItemsToPlay.size() == 0) {
Utils.showToast(mApplication, mApplication.getString(R.string.msg_no_playable_items));
mActivity.finish();
@@ -231,14 +231,13 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mApplication.setPlaybackController(new PlaybackController(mItemsToPlay, this));
mPlaybackController = mApplication.getPlaybackController();
//set up fade task
// setup fade task
mHideTask = new Runnable() {
@Override
public void run() {
if (mIsVisible) hide();
}
};
}
@Nullable
@@ -246,13 +245,13 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.vlc_player_interface, container);
// Inject the RowsFragment in the popup container
// inject the RowsSupportFragment in the popup container
if (getChildFragmentManager().findFragmentById(R.id.rows_area) == null) {
mPopupRowsFragment = new RowsFragment();
mPopupRowsFragment = new RowsSupportFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.rows_area, mPopupRowsFragment).commit();
} else {
mPopupRowsFragment = (RowsFragment) getChildFragmentManager()
mPopupRowsFragment = (RowsSupportFragment) getChildFragmentManager()
.findFragmentById(R.id.rows_area);
}
@@ -295,12 +294,11 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mTopPanel = mActivity.findViewById(R.id.topPanel);
mBottomPanel = mActivity.findViewById(R.id.bottomPanel);
mNextUpPanel = mActivity.findViewById(R.id.nextUpPanel);
mSmNextUpPanel = mActivity.findViewById(R.id.smNextUpPanel);
mPlayPauseBtn = mActivity.findViewById(R.id.playPauseBtn);
mPlayPauseBtn.setSecondaryImage(R.drawable.lb_ic_pause);
mPlayPauseBtn.setPrimaryImage(R.drawable.play);
mPlayPauseBtn.setSecondaryImage(R.drawable.ic_pause);
mPlayPauseBtn.setPrimaryImage(R.drawable.ic_play);
mPlayPauseBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -424,7 +422,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mChannels.setFocusable(false);
mChannelScroller.setFocusable(false);
//Register to receive message from popup
// register to receive message from popup
mActivity.registerMessageListener(new IMessageListener() {
@Override
public void onMessageReceived(CustomMessage message) {
@@ -435,12 +433,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
Intent intent = mActivity.getIntent();
//start playing
int startPos = intent.getIntExtra("Position", 0);
// start playing
mPlaybackController.play(startPos);
mPlayPauseBtn.requestFocus();
}
private void setupPopupAnimations() {
@@ -449,7 +447,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationStart(Animation animation) {
mPopupArea.setVisibility(View.VISIBLE);
}
@Override
@@ -459,14 +456,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
});
hidePopup = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
hidePopup.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
@@ -477,11 +472,8 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void setupNextUpAnimations() {
@@ -490,7 +482,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationStart(Animation animation) {
mNextUpPanel.setVisibility(View.VISIBLE);
}
@Override
@@ -500,14 +491,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
});
hideNextUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
hideNextUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
@@ -517,7 +506,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
});
@@ -526,7 +514,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationStart(Animation animation) {
mSmNextUpPanel.setVisibility(View.VISIBLE);
}
@Override
@@ -536,14 +523,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
});
hideSmNextUp = AnimationUtils.loadAnimation(mActivity, R.anim.abc_fade_out);
hideSmNextUp.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
@@ -553,11 +538,8 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private AudioManager.OnAudioFocusChangeListener mAudioFocusChanged = new AudioManager.OnAudioFocusChangeListener() {
@@ -568,7 +550,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mPlaybackController.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS:
//We don't do anything here on purpose
// We don't do anything here on purpose
// On the Nexus we get this notification erroneously when first starting up
// and in any instance that we navigate away from our page, we already handle
// stopping video and handing back audio focus
@@ -583,9 +565,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof BaseRowItem) {
BaseRowItem rowItem = (BaseRowItem)item;
switch (rowItem.getItemType()) {
case Chapter:
Long start = rowItem.getChapterInfo().getStartPositionTicks() / 10000;
mPlaybackController.seek(start);
@@ -603,21 +583,22 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
private View.OnKeyListener keyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP && mActivity != null && !mActivity.isFinishing()) {
mActivity.finish();
return true;
}
if (mPopupPanelVisible && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE)) {
//back should just hide the popup panel
// back should just hide the popup panel
hidePopupPanel();
if (mPlaybackController.isLiveTv()) hide(); //also close this if live tv
// also close this if live tv
if (mPlaybackController.isLiveTv()) hide();
return true;
}
if (mGuideVisible) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B || keyCode == KeyEvent.KEYCODE_ESCAPE) {
//go back to normal
// go back to normal
hideGuide();
return true;
} else if ((keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) &&
@@ -626,8 +607,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
Utils.beep();
switchChannel(mSelectedProgram.getChannelId());
return true;
}
else {
} else {
return false;
}
}
@@ -715,7 +695,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
private void switchChannel(String id) {
if (id == null) return;
if (mPlaybackController.getCurrentlyPlayingItem().getId().equals(id)) {
//same channel, just dismiss overlay
// same channel, just dismiss overlay
hideGuide();
} else {
mPlaybackController.stop();
@@ -753,7 +733,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
return;
}
//Register a media button receiver so that all media button presses will come to us and not another app
// register a media button receiver so that all media button presses will come to us and not another app
mAudioManager.registerMediaButtonEventReceiver(new ComponentName(TvApp.getApplication().getPackageName(), RemoteControlReceiver.class.getName()));
//TODO implement conditional logic for api 21+
@@ -773,23 +753,22 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
public void onDestroy() {
super.onDestroy();
if (mPlaybackController != null) {
if (mPlaybackController.getCurrentlyPlayingItem() != null) RecommendationManager.getInstance().recommend(mPlaybackController.getCurrentlyPlayingItem().getId());
mPlaybackController.removePreviousQueueItems();
}
}
@Override
public void onPause() {
super.onPause();
mPlaybackController.stop();
setPlayPauseActionState(ImageButton.STATE_PRIMARY); // in case we come back
//Give back audio focus
// in case we come back
setPlayPauseActionState(ImageButton.STATE_PRIMARY);
// give back audio focus
mAudioManager.abandonAudioFocus(mAudioFocusChanged);
mApplication.getLogger().Debug("Fragment pausing. IsFinishing: "+mActivity.isFinishing());
if (!mActivity.isFinishing()) mActivity.finish(); // user hit "home" we want to back out
}
public void show() {
@@ -911,8 +890,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
});
}
//
}
public void displayChannels(int start, int max) {
@@ -941,7 +918,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
DisplayProgramsTask mDisplayProgramsTask;
class DisplayProgramsTask extends AsyncTask<Integer, Integer, Void> {
View firstRow;
int displayedChannels = 0;
@@ -1010,7 +986,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
});
displayedChannels++;
}
return null;
}
@@ -1036,16 +1011,13 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mGuideSpinner.setVisibility(View.GONE);
if (firstRow != null) firstRow.requestFocus();
}
}
private int currentCellId = 0;
private LinearLayout getProgramRow(List<BaseItemDto> programs, String channelId) {
LinearLayout programRow = new LinearLayout(mActivity);
if (programs.size() == 0) {
BaseItemDto empty = new BaseItemDto();
empty.setName(mApplication.getString(R.string.no_program_data));
@@ -1083,7 +1055,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (end > getCurrentLocalEndDate()) end = getCurrentLocalEndDate();
prevEnd = end;
Long duration = (end - start) / 60000;
//TvApp.getApplication().getLogger().Debug("Duration for "+item.getName()+" is "+duration.intValue());
if (duration > 0) {
ProgramGridCell program = new ProgramGridCell(mActivity, mFragment, item);
program.setId(currentCellId++);
@@ -1091,9 +1062,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
program.setFocusable(true);
programRow.addView(program);
}
}
return programRow;
@@ -1124,7 +1093,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
//after first one, we always go on hours
interval = 60;
}
}
private Runnable detailUpdateTask = new Runnable() {
@@ -1144,7 +1112,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
detailUpdateInternal();
}
});
} else {
detailUpdateInternal();
}
@@ -1159,7 +1126,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
//info row
InfoLayoutHelper.addInfoRow(mActivity, mSelectedProgram, mGuideInfoRow, false, false);
} else {
mGuideInfoRow.removeAllViews();
}
@@ -1194,7 +1160,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
private Animation.AnimationListener hideAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
@@ -1205,14 +1170,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
};
private Animation.AnimationListener showAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
@@ -1223,7 +1186,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void onAnimationRepeat(Animation animation) {
}
};
@@ -1233,9 +1195,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
int width = Utils.convertDpToPixel(getActivity(), 150);
String posterImageUrl = ImageUtils.getPrimaryImageUrl(item, mApplication.getApiClient(), false, false, preferSeries, height);
if (posterImageUrl != null) Picasso.with(getActivity()).load(posterImageUrl).skipMemoryCache().resize(width, height).centerInside().into(target);
}
}
private void updateLogo(BaseItemDto item, ImageView target) {
@@ -1268,10 +1228,8 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
if (studioImageUrl != null)
Picasso.with(mActivity).load(studioImageUrl).resize(width, height).centerInside().into(mStudioImage);
} else {
mStudioImage.setImageResource(R.drawable.blank30x30);
}
}
}
@@ -1287,7 +1245,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
);
}
});
}
private void updateCurrentDuration(BaseItemDto item) {
@@ -1305,7 +1262,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (ndx > 0) {
mPopupRowPresenter.setPosition(ndx);
}
}
},500);
}
@@ -1315,7 +1271,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (!DeviceUtils.isFireTv() && mPlaybackController.canSeek()) {
// on-screen jump buttons for Nexus
mButtonRow.addView(new ImageButton(mActivity, R.drawable.repeat, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_loop, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.skip(-11000);
@@ -1323,20 +1279,19 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
}));
mButtonRow.addView(new ImageButton(mActivity, R.drawable.fastforward, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_fastforward, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mPlaybackController.skip(30000);
startFadeTimer();
}
}));
}
if (mPlaybackController.isLiveTv()) {
// prev channel button
// previous channel button
if (TvManager.getPrevLiveTvChannel() != null) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.prev, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_previous_episode, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
switchChannel(TvManager.getPrevLiveTvChannel());
@@ -1344,7 +1299,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}));
}
//Create quick channel change row
// create quick channel change row
TvManager.loadAllChannels(new Response<Integer>() {
@Override
public void onResponse(Integer response) {
@@ -1353,11 +1308,10 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (mChapterRow != null) mPopupRowAdapter.remove(mChapterRow);
mChapterRow = new ListRow(new HeaderItem("Channels"), channelAdapter);
mPopupRowAdapter.add(mChapterRow);
}
});
mButtonRow.addView(new ImageButton(mActivity, R.drawable.channelbar, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_channel_bar, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showQuickChannelChanger();
@@ -1365,7 +1319,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}));
// guide button
mButtonRow.addView(new ImageButton(mActivity, R.drawable.guidebutton, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_guide, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showGuide();
@@ -1374,7 +1328,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
// record button
if (item.getCurrentProgram() != null && mApplication.canManageRecordings()) {
mButtonRow.addView(new ImageButton(mActivity, item.getCurrentProgram().getTimerId() != null ? R.drawable.rec : R.drawable.recwhite, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, item.getCurrentProgram().getTimerId() != null ? R.drawable.ic_record_red : R.drawable.ic_record, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleRecording(mPlaybackController.getCurrentlyPlayingItem());
@@ -1382,15 +1336,14 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}));
}
//adjust popup for channels
// adjust popup for channels
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mPopupArea.getLayoutParams();
params.height = Utils.convertDpToPixel(mActivity, 170);
mPopupArea.setLayoutParams(params);
}
if (!TextUtils.isEmpty(item.getOverview())) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.infoicon, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_info, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showInfo();
@@ -1398,12 +1351,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}));
}
Boolean hasSubs = StreamHelper.getSubtitleStreams(mPlaybackController.getCurrentMediaSource()).size() > 0;
Boolean hasMultiAudio = StreamHelper.getAudioStreams(mPlaybackController.getCurrentMediaSource()).size() > 1;
boolean hasSubs = StreamHelper.getSubtitleStreams(mPlaybackController.getCurrentMediaSource()).size() > 0;
boolean hasMultiAudio = StreamHelper.getAudioStreams(mPlaybackController.getCurrentMediaSource()).size() > 1;
if (hasMultiAudio) {
mApplication.getLogger().Debug("Multiple Audio tracks found: "+ StreamHelper.getAudioStreams(mPlaybackController.getCurrentMediaSource()).size());
mButtonRow.addView(new ImageButton(mActivity, R.drawable.audiosel, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_select_audio, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPlaybackController.getCurrentStreamInfo() == null) {
@@ -1420,7 +1373,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
currentAudioIndex = mPlaybackController.translateVlcAudioId(currentAudioIndex);
}
PopupMenu audioMenu = Utils.createPopupMenu(getActivity(), v, Gravity.RIGHT);
PopupMenu audioMenu = Utils.createPopupMenu(getActivity(), v, Gravity.END);
for (MediaStream audio : audioTracks) {
MenuItem item = audioMenu.getMenu().add(0, audio.getIndex(), audio.getIndex(), audio.getDisplayTitle());
if (currentAudioIndex != null && currentAudioIndex == audio.getIndex()) item.setChecked(true);
@@ -1441,7 +1394,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
});
audioMenu.show();
}
}));
} else {
@@ -1450,7 +1402,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (hasSubs) {
mApplication.getLogger().Debug("Subtitle tracks found: " + mPlaybackController.getSubtitleStreams().size());
mButtonRow.addView(new ImageButton(mActivity, R.drawable.subt, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_select_subtitle, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPlaybackController.getCurrentStreamInfo() == null) {
@@ -1460,7 +1412,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
setFadingEnabled(false);
List<SubtitleStreamInfo> subtitles = mPlaybackController.getSubtitleStreams();
PopupMenu subMenu = Utils.createPopupMenu(getActivity(), v, Gravity.RIGHT);
PopupMenu subMenu = Utils.createPopupMenu(getActivity(), v, Gravity.END);
MenuItem none = subMenu.getMenu().add(0, -1, 0, mApplication.getString(R.string.lbl_none));
int currentSubIndex = mPlaybackController.getSubtitleStreamIndex();
if (currentSubIndex < 0) none.setChecked(true);
@@ -1484,7 +1436,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
});
subMenu.show();
}
}));
} else {
@@ -1493,7 +1444,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
List<ChapterInfoDto> chapters = item.getChapters();
if (chapters != null && chapters.size() > 0) {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.chapter, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_select_chapter, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
showChapterPanel();
@@ -1504,13 +1455,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (ndx > 0) {
mPopupRowPresenter.setPosition(ndx);
}
}
},500);
}
}));
//Create chapter row for later use
// create chapter row for later use
ItemRowAdapter chapterAdapter = new ItemRowAdapter(BaseItemUtils.buildChapterItems(item), new CardPresenter(true, 220), new ArrayObjectAdapter());
chapterAdapter.Retrieve();
if (mChapterRow != null) mPopupRowAdapter.remove(mChapterRow);
@@ -1528,7 +1478,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}));
}
if (!mPlaybackController.isNativeMode()) {
if (mAudioPopup == null ) mAudioPopup = new AudioDelayPopup(mActivity, mBottomPanel, new ValueChangedListener<Long>() {
@Override
@@ -1536,14 +1485,14 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mPlaybackController.setAudioDelay(value);
}
});
mButtonRow.addView(new ImageButton(mActivity, R.drawable.adjust, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_adjust, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
mAudioPopup.show(mPlaybackController.getAudioDelay());
}
}));
} else {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.zoom, mButtonSize, new View.OnClickListener() {
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_zoom, mButtonSize, new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu zoomMenu = Utils.createPopupMenu(mActivity, v, Gravity.RIGHT);
@@ -1571,9 +1520,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
zoomMenu.show();
}
}));
}
}
private int getCurrentChapterIndex(BaseItemDto item, long pos) {
@@ -1650,7 +1597,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
private void cancelRecording(BaseItemDto program, boolean series) {
if (program != null) {
if (series) {
mApplication.getApiClient().CancelLiveTvSeriesTimerAsync(program.getSeriesTimerId(), new EmptyResponse() {
@@ -1680,15 +1626,11 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
Utils.showToast(mActivity, R.string.msg_unable_to_cancel);
}
});
}
}
}
private void recordProgram(final BaseItemDto program, final boolean series) {
// Utils.showToast(mActivity, "Not Yet Implemented");
// return;
if (program != null) {
mApplication.getApiClient().GetDefaultLiveTvTimerInfo(new Response<SeriesTimerInfoDto>() {
@Override
@@ -1726,7 +1668,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
});
}
}
AudioDelayPopup mAudioPopup;
@@ -1782,12 +1723,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (mNextUpPanelVisible) hideNextUpPanel();
if (mSmNextUpPanelVisible) hideSmNextUpPanel();
updateCurrentDuration(current);
//set progress to match duration
// set progress to match duration
mCurrentProgress.setMax(mCurrentDuration);
//set other information
// set other information
mTitle.setText(current.getName());
mInfoSummary.setText(current.getOverview());
mGuideCurrentTitle.setText(" "+current.getName());
mGuideCurrentTitle.setText(current.getName());
updatePoster(current, mPoster, true);
updateLogo(current, mLogoImage);
updateStudio(current);
@@ -1798,7 +1739,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
StreamInfo stream = mPlaybackController.getCurrentStreamInfo();
if (stream != null) {
switch (stream.getPlayMethod()) {
case Transcode:
InfoLayoutHelper.addBlockText(mActivity, mInfoRow, "Trans" + (mPlaybackController.mVideoManager.isNativeMode() ? "/I" : "/V"));
break;
@@ -1817,7 +1757,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
@Override
public void nextItemThresholdHit(BaseItemDto nextItem) {
mApplication.getLogger().Debug("Next Item is " + nextItem.getName());
//we need to retrieve full item for all info
// need to retrieve full item for all info
mApplication.getApiClient().GetItemAsync(nextItem.getId(), mApplication.getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
@@ -1840,10 +1780,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
private long lastReportedPosMs;
private void updateManualSubtitlePosition() {
/*
* Adjust subtitles margin based on Screen dimes
*/
// adjust subtitles margin based on screen dimension
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mSubtitleText.getLayoutParams();
DisplayMetrics dm = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
@@ -1863,7 +1800,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
mSubtitleText.setText("");
}
});
}
public void showSubLoadingMsg(final boolean show) {
@@ -1882,7 +1818,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
public void updateSubtitles(long positionMs) {
if (lastReportedPosMs > 0){
if (Math.abs(lastReportedPosMs - positionMs) < 500) {
return;
@@ -1894,7 +1829,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
long positionTicks = positionMs * 10000;
for (SubtitleTrackEvent caption : mManualSubs.getTrackEvents()) {
if (positionTicks >= caption.getStartPositionTicks() && positionTicks <= caption.getEndPositionTicks()) {
setTimedText(caption);
@@ -1906,7 +1840,6 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
private void setTimedText(final SubtitleTrackEvent textObj) {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1916,14 +1849,16 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
}
String text = textObj.getText();
if (text == null || text.length() == 0) {
mSubtitleText.setVisibility(View.INVISIBLE);
return;
}
mSubtitleText.setText(Html.fromHtml(text));
SpannableString span = new SpannableString(Html.fromHtml(text));
span.setSpan(new ForegroundColorSpan(Color.WHITE), 0, span.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
mSubtitleText.setText(span);
mSubtitleText.setVisibility(View.VISIBLE);
}
});
}}
}
}

View File

@@ -1,6 +1,5 @@
package org.jellyfin.androidtv.playback;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
@@ -9,7 +8,6 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
@@ -19,15 +17,18 @@ import org.jellyfin.androidtv.model.compat.VideoOptions;
import org.jellyfin.androidtv.util.ProfileHelper;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.ReportingHelper;
import java.util.List;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
import org.jellyfin.apiclient.model.session.PlayMethod;
public class ExternalPlayerActivity extends Activity {
import java.util.List;
import androidx.fragment.app.FragmentActivity;
import androidx.leanback.app.BackgroundManager;
public class ExternalPlayerActivity extends FragmentActivity {
List<BaseItemDto> mItemsToPlay;
TvApp mApplication = TvApp.getApplication();
@@ -133,7 +134,7 @@ public class ExternalPlayerActivity extends Activity {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = mApplication.getPrefs();
prefs.edit().putBoolean("pref_video_use_external", false).apply();
prefs.edit().putString("pref_video_player", "auto").apply();
prefs.edit().putBoolean("pref_live_tv_use_external", false).apply();
}
})
@@ -205,7 +206,7 @@ public class ExternalPlayerActivity extends Activity {
//Get playback info for current item
mCurrentNdx = ndx;
final BaseItemDto item = mItemsToPlay.get(mCurrentNdx);
isLiveTv = item.getType().equals("TvChannel");
isLiveTv = item.getBaseItemType() == BaseItemType.TvChannel;
if (!isLiveTv && mApplication.getPrefs().getBoolean("pref_send_path_external", false)) {
// Just pass the path directly

View File

@@ -3,9 +3,6 @@ package org.jellyfin.androidtv.playback;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.mediainfo.SubtitleTrackInfo;
/**
* Created by Eric on 4/28/2015.
*/
public interface IPlaybackOverlayFragment {
void setCurrentTime(long time);
void setSecondaryTime(long time);

View File

@@ -7,14 +7,18 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.text.InputType;
import android.widget.EditText;
import com.devbrackets.android.exomedia.EMAudioPlayer;
import com.devbrackets.android.exomedia.event.EMMediaProgressEvent;
import com.devbrackets.android.exomedia.listener.EMProgressCallback;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
@@ -32,6 +36,12 @@ import org.jellyfin.androidtv.util.RemoteControlReceiver;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.BaseItemUtils;
import org.jellyfin.androidtv.util.apiclient.ReportingHelper;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.playlists.PlaylistCreationRequest;
import org.jellyfin.apiclient.model.playlists.PlaylistCreationResult;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
@@ -41,13 +51,6 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.playlists.PlaylistCreationRequest;
import org.jellyfin.apiclient.model.playlists.PlaylistCreationResult;
public class MediaManager {
private static ItemRowAdapter mCurrentMediaAdapter;
private static int mCurrentMediaPosition = -1;
@@ -63,7 +66,7 @@ public class MediaManager {
private static LibVLC mLibVLC;
private static org.videolan.libvlc.MediaPlayer mVlcPlayer;
private static VlcEventHandler mVlcHandler = new VlcEventHandler();
private static EMAudioPlayer mExoplayer;
private static SimpleExoPlayer mExoPlayer;
private static AudioManager mAudioManager;
private static boolean audioInitialized;
private static boolean nativeMode = false;
@@ -157,7 +160,7 @@ public class MediaManager {
}
private static boolean isPaused() {
return nativeMode ? !mExoplayer.isPlaying() : !mVlcPlayer.isPlaying();
return nativeMode ? !mExoPlayer.isPlaying() : !mVlcPlayer.isPlaying();
}
private static void reportProgress() {
@@ -165,7 +168,7 @@ public class MediaManager {
if (System.currentTimeMillis() < lastProgressEvent + 750) return;
lastProgressEvent = System.currentTimeMillis();
mCurrentAudioPosition = nativeMode ? mExoplayer.getCurrentPosition() : mVlcPlayer.getTime();
mCurrentAudioPosition = nativeMode ? mExoPlayer.getCurrentPosition() : mVlcPlayer.getTime();
//fire external listeners if there
for (AudioEventListener listener : mAudioEventListeners) {
@@ -199,19 +202,21 @@ public class MediaManager {
// Create a new media player based on platform
if (DeviceUtils.is60()) {
nativeMode = true;
mExoplayer = new EMAudioPlayer(TvApp.getApplication());
mExoplayer.setProgressCallback(new EMProgressCallback() {
mExoPlayer = ExoPlayerFactory.newSimpleInstance(TvApp.getApplication());
mExoPlayer.addListener(new Player.EventListener() {
@Override
public boolean onProgressUpdated(EMMediaProgressEvent progressEvent) {
reportProgress();
return false;
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_READY) {
startProgressLoop();
} else if (playbackState == Player.STATE_ENDED) {
onComplete();
stopProgressLoop();
}
}
});
mExoplayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
onComplete();
public void onPlayerError(ExoPlaybackException error) {
stopProgressLoop();
}
});
} else {
@@ -220,17 +225,7 @@ public class MediaManager {
options.add("--no-audio-time-stretch");
options.add("-v");
mLibVLC = new LibVLC(options);
LibVLC.setOnNativeCrashListener(new LibVLC.OnNativeCrashListener() {
@Override
public void onNativeCrash() {
new Exception().printStackTrace();
//todo put our custom log reporter here...
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
});
mLibVLC = new LibVLC(TvApp.getApplication(), options);
mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
mVlcPlayer.setAudioOutput(Utils.downMixAudio() ? "opensles_android" : "android_audiotrack");
@@ -263,6 +258,25 @@ public class MediaManager {
return true;
}
private static Runnable progressLoop;
private static Handler mHandler = new Handler();
private static void startProgressLoop() {
progressLoop = new Runnable() {
@Override
public void run() {
reportProgress();
mHandler.postDelayed(this, 500);
}
};
mHandler.post(progressLoop);
}
private static void stopProgressLoop() {
if (progressLoop != null) {
mHandler.removeCallbacks(progressLoop);
}
}
private static AudioManager.OnAudioFocusChangeListener mAudioFocusChanged = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
@@ -281,6 +295,13 @@ public class MediaManager {
}
};
private static void fireQueueReplaced(){
for (AudioEventListener listener : mAudioEventListeners) {
TvApp.getApplication().getLogger().Info("Firing queue replaced listener. ");
listener.onQueueReplaced();
}
}
private static void fireQueueStatusChange() {
for (AudioEventListener listener : mAudioEventListeners) {
TvApp.getApplication().getLogger().Info("Firing queue state change listener. "+ hasAudioQueueItems());
@@ -370,7 +391,7 @@ public class MediaManager {
public static int queueAudioItem(int pos, BaseItemDto item) {
if (mCurrentAudioQueue == null) createAudioQueue(new ArrayList<BaseItemDto>());
mCurrentAudioQueue.add(new BaseRowItem(pos, item));
TvApp.getApplication().showMessage(TvApp.getApplication().getString(R.string.msg_added_item_to_queue) + (pos + 1), BaseItemUtils.getFullName(item), 4000, R.drawable.audioicon);
TvApp.getApplication().showMessage(TvApp.getApplication().getString(R.string.msg_added_item_to_queue) + (pos + 1), BaseItemUtils.getFullName(item), 4000, R.drawable.ic_album);
return pos;
}
@@ -421,7 +442,7 @@ public class MediaManager {
}
fireQueueStatusChange();
}
TvApp.getApplication().showMessage(items.size() + (items.size() > 1 ? TvApp.getApplication().getString(R.string.msg_items_added) : TvApp.getApplication().getString(R.string.msg_item_added)), mCurrentAudioQueue.size() + TvApp.getApplication().getString(R.string.msg_total_items_in_queue), 5000, R.drawable.audioicon);
TvApp.getApplication().showMessage(items.size() + (items.size() > 1 ? TvApp.getApplication().getString(R.string.msg_items_added) : TvApp.getApplication().getString(R.string.msg_item_added)), mCurrentAudioQueue.size() + TvApp.getApplication().getString(R.string.msg_total_items_in_queue), 5000, R.drawable.ic_album);
}
public static void removeFromAudioQueue(int ndx) {
@@ -457,7 +478,7 @@ public class MediaManager {
}
}
public static boolean isPlayingAudio() { return audioInitialized && (nativeMode ? mExoplayer.isPlaying() : mVlcPlayer.isPlaying()); }
public static boolean isPlayingAudio() { return audioInitialized && (nativeMode ? mExoPlayer.isPlaying() : mVlcPlayer.isPlaying()); }
private static boolean ensureInitialized() {
if (!audioInitialized) {
@@ -482,6 +503,7 @@ public class MediaManager {
@Override
public void onClick(DialogInterface dialog, int which) {
playNowInternal(items);
fireQueueReplaced();
}
})
.setNeutralButton(R.string.lbl_add_to_queue, new DialogInterface.OnClickListener() {
@@ -495,7 +517,6 @@ public class MediaManager {
} else {
playNowInternal(items);
}
}
private static void playNowInternal(List<BaseItemDto> items) {
@@ -506,7 +527,7 @@ public class MediaManager {
Intent nowPlaying = new Intent(TvApp.getApplication(), AudioNowPlayingActivity.class);
TvApp.getApplication().getCurrentActivity().startActivity(nowPlaying);
} else {
TvApp.getApplication().showMessage(items.size() + (items.size() > 1 ? TvApp.getApplication().getString(R.string.msg_items_added) : TvApp.getApplication().getString(R.string.msg_item_added)), mCurrentAudioQueue.size() + TvApp.getApplication().getString(R.string.msg_total_items_in_queue), 5000, R.drawable.audioicon);
TvApp.getApplication().showMessage(items.size() + (items.size() > 1 ? TvApp.getApplication().getString(R.string.msg_items_added) : TvApp.getApplication().getString(R.string.msg_item_added)), mCurrentAudioQueue.size() + TvApp.getApplication().getString(R.string.msg_total_items_in_queue), 5000, R.drawable.ic_album);
}
}
@@ -567,8 +588,10 @@ public class MediaManager {
mCurrentAudioQueuePosition = pos;
mCurrentAudioPosition = 0;
if (nativeMode) {
mExoplayer.setDataSource(TvApp.getApplication(), Uri.parse(response.ToUrl(apiClient.getApiUrl(), apiClient.getAccessToken())));
mExoplayer.start();
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(TvApp.getApplication(), "ATV/ExoPlayer");
mExoPlayer.setPlayWhenReady(true);
mExoPlayer.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(response.ToUrl(apiClient.getApiUrl(), apiClient.getAccessToken()))));
} else {
TvApp.getApplication().getLogger().Info("Playback attempt via VLC of " + response.getMediaUrl());
Media media = new Media(mLibVLC, Uri.parse(response.getMediaUrl()));
@@ -670,7 +693,7 @@ public class MediaManager {
if (mCurrentAudioQueue == null || (!mRepeat && mCurrentAudioQueue.size() == 0)) return -1;
if (isPlayingAudio() && mCurrentAudioPosition > 10000) {
//just back up to the beginning of current item
if (nativeMode) mExoplayer.seekTo(0);
if (nativeMode) mExoPlayer.seekTo(0);
else mVlcPlayer.setTime(0);
return mCurrentAudioQueuePosition;
}
@@ -692,7 +715,7 @@ public class MediaManager {
}
private static void stop() {
if (nativeMode) mExoplayer.stopPlayback();
if (nativeMode) mExoPlayer.stop(true);
else mVlcPlayer.stop();
}
@@ -711,7 +734,7 @@ public class MediaManager {
}
private static void pause() {
if (nativeMode) mExoplayer.pause();
if (nativeMode) mExoPlayer.setPlayWhenReady(false);
else mVlcPlayer.pause();
}
@@ -733,7 +756,7 @@ public class MediaManager {
public static void resumeAudio() {
if (mCurrentAudioItem != null) {
ensureAudioFocus();
if (nativeMode) mExoplayer.start();
if (nativeMode) mExoPlayer.setPlayWhenReady(true);
else mVlcPlayer.play();
updateCurrentAudioItemPlaying(true);
ReportingHelper.reportStart(mCurrentAudioItem, mCurrentAudioPosition * 10000);

View File

@@ -10,6 +10,7 @@ import android.view.WindowManager;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.constants.ContainerTypes;
import org.jellyfin.androidtv.livetv.TvManager;
import org.jellyfin.androidtv.model.compat.PlaybackException;
import org.jellyfin.androidtv.model.compat.StreamInfo;
@@ -23,26 +24,39 @@ import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.PlaybackHelper;
import org.jellyfin.androidtv.util.apiclient.ReportingHelper;
import org.jellyfin.androidtv.util.apiclient.StreamHelper;
import java.util.List;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
import org.jellyfin.apiclient.model.dlna.DirectPlayProfile;
import org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.BaseItemType;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.entities.LocationType;
import org.jellyfin.apiclient.model.entities.MediaStream;
import org.jellyfin.apiclient.model.entities.MediaStreamType;
import org.jellyfin.apiclient.model.extensions.StringHelper;
import org.jellyfin.apiclient.model.library.PlayAccess;
import org.jellyfin.apiclient.model.livetv.ChannelInfoDto;
import org.jellyfin.apiclient.model.mediainfo.SubtitleTrackInfo;
import org.jellyfin.apiclient.model.session.PlayMethod;
import java.util.List;
public class PlaybackController {
// Frequency to report playback progress
private final static long PROGRESS_REPORTING_INTERVAL = TimeUtils.secondsToMillis(3);
// Frequency to report paused state
private static final long PROGRESS_REPORTING_PAUSE_INTERVAL = TimeUtils.secondsToMillis(15);
// Minimum duration to show next up popup
private static final long NEXT_UP_MIN_LENGTH = TimeUtils.minutesToMillis(10);
// Minimum duration to show next up popup for longer
private static final long NEXT_UP_LONG_LENGTH = TimeUtils.hoursToMillis(1.25);
// The duration to display the next up popup for longer shows
private static final long NEXT_UP_LONG_DURATION = TimeUtils.minutesToMillis(3);
// The default duration to display the next up popup
private static final long NEXT_UP_DURATION = TimeUtils.secondsToMillis(30);
List<BaseItemDto> mItems;
VideoManager mVideoManager;
SubtitleHelper mSubHelper;
@@ -66,7 +80,6 @@ public class PlaybackController {
private Runnable mReportLoop;
private Handler mHandler;
private static int REPORT_INTERVAL = 3000;
private long mNextItemThreshold = Long.MAX_VALUE;
private boolean nextItemReported;
@@ -77,7 +90,7 @@ public class PlaybackController {
private boolean isLiveTv;
private boolean directStreamLiveTv;
private String liveTvChannelName = "";
private boolean useVlc = false;
private boolean useVlc;
private boolean vlcErrorEncountered;
private boolean exoErrorEncountered;
@@ -98,6 +111,9 @@ public class PlaybackController {
refreshRateSwitchingEnabled = DeviceUtils.is60() && mApplication.getPrefs().getBoolean("pref_refresh_switching", false);
if (refreshRateSwitchingEnabled) getDisplayModes();
// Set default value for useVlc field
// when set to auto the default will be exoplayer
useVlc = mApplication.getPrefs().getString("pref_video_player", "auto").equals("vlc");
}
public void init(VideoManager mgr, View spinner) {
@@ -318,7 +334,7 @@ public class PlaybackController {
return;
}
isLiveTv = item.getType().equals("TvChannel");
isLiveTv = item.getBaseItemType() == BaseItemType.TvChannel;
startSpinner();
//Build options for each player
@@ -374,17 +390,17 @@ public class PlaybackController {
if (hasNextItem()) {
// Determine the "next up" threshold
if (duration > 600000) {
if (duration > NEXT_UP_MIN_LENGTH) {
//only items longer than 10min to have this feature
nextItemReported = false;
if (duration > 4500000) {
if (duration > NEXT_UP_LONG_LENGTH) {
//longer than 1hr 15 it probably has pretty long credits
mNextItemThreshold = duration - 180000; // 3 min
mNextItemThreshold = duration - NEXT_UP_LONG_DURATION;
} else {
//std 30 min episode or less
mNextItemThreshold = duration - 50000; // 50 seconds
mNextItemThreshold = duration - NEXT_UP_DURATION;
}
TvApp.getApplication().getLogger().Debug("Next item threshold set to "+ mNextItemThreshold);
TvApp.getApplication().getLogger().Debug("Next item threshold set to " + mNextItemThreshold);
} else {
mNextItemThreshold = Long.MAX_VALUE;
}
@@ -454,27 +470,44 @@ public class PlaybackController {
@Override
public void onResponse(StreamInfo internalResponse) {
mApplication.getLogger().Info("Internal player would " + (internalResponse.getPlayMethod().equals(PlayMethod.Transcode) ? "transcode" : "direct stream"));
boolean useDeinterlacing = vlcResponse.getMediaSource().getVideoStream().getIsInterlaced() && (vlcResponse.getMediaSource().getVideoStream().getWidth() == null || vlcResponse.getMediaSource().getVideoStream().getWidth() > 1200);
boolean useDeinterlacing = vlcResponse.getMediaSource().getVideoStream() != null &&
vlcResponse.getMediaSource().getVideoStream().getIsInterlaced() &&
(vlcResponse.getMediaSource().getVideoStream().getWidth() == null ||
vlcResponse.getMediaSource().getVideoStream().getWidth() > 1200);
mApplication.getLogger().Info(useDeinterlacing ? "Explicit deinterlacing will be used" : "Explicit deinterlacing will NOT be used");
// TODO: Clean up this logic
// Now look at both responses and choose the one that direct plays or bitstreams - favor VLC
useVlc = !vlcErrorEncountered &&
!vlcResponse.getPlayMethod().equals(PlayMethod.Transcode) &&
(DeviceUtils.is60() ||
!mApplication.getPrefs().getBoolean("pref_bitstream_ac3", false) ||
(!"ac3".equals(vlcResponse.getMediaSource().getDefaultAudioStream().getCodec()) &&
!"truehd".equals(vlcResponse.getMediaSource().getDefaultAudioStream().getCodec()))) &&
(Utils.downMixAudio() ||
!DeviceUtils.is60() ||
internalResponse.getPlayMethod().equals(PlayMethod.Transcode) ||
!mApplication.getPrefs().getBoolean("pref_bitstream_dts", false) ||
internalResponse.getMediaSource() == null ||
internalResponse.getMediaSource().getDefaultAudioStream() == null ||
(!internalResponse.getMediaSource().getDefaultAudioStream().getCodec().equals("dca") &&
!internalResponse.getMediaSource().getDefaultAudioStream().getCodec().equals("dts"))) &&
(!DeviceUtils.isFireTvStick() |
(vlcResponse.getMediaSource().getVideoStream() != null && vlcResponse.getMediaSource().getVideoStream().getWidth() < 1000));
String preferredVideoPlayer = mApplication.getPrefs().getString("pref_video_player", "auto");
mApplication.getLogger().Info("User preferred player is: " + preferredVideoPlayer);
if (preferredVideoPlayer.equals("vlc")) {
// Force VLC
useVlc = true;
} else if (preferredVideoPlayer.equals("exoplayer")) {
// Make sure to not use VLC
useVlc = false;
} else if (preferredVideoPlayer.equals("auto")) {
// TODO: Clean up this logic
// Now look at both responses and choose the one that direct plays or bitstreams - favor VLC
useVlc = !vlcErrorEncountered &&
!vlcResponse.getPlayMethod().equals(PlayMethod.Transcode) &&
(DeviceUtils.is60() ||
!mApplication.getPrefs().getBoolean("pref_bitstream_ac3", false) ||
vlcResponse.getMediaSource() == null ||
vlcResponse.getMediaSource().getDefaultAudioStream() == null ||
(!"ac3".equals(vlcResponse.getMediaSource().getDefaultAudioStream().getCodec()) &&
!"truehd".equals(vlcResponse.getMediaSource().getDefaultAudioStream().getCodec()))) &&
(Utils.downMixAudio() ||
!DeviceUtils.is60() ||
internalResponse.getPlayMethod().equals(PlayMethod.Transcode) ||
!mApplication.getPrefs().getBoolean("pref_bitstream_dts", false) ||
internalResponse.getMediaSource() == null ||
internalResponse.getMediaSource().getDefaultAudioStream() == null ||
(!internalResponse.getMediaSource().getDefaultAudioStream().getCodec().equals("dca") &&
!internalResponse.getMediaSource().getDefaultAudioStream().getCodec().equals("dts"))) &&
(!DeviceUtils.isFireTvStick() ||
(vlcResponse.getMediaSource().getVideoStream() != null && vlcResponse.getMediaSource().getVideoStream().getWidth() < 1000));
}
mApplication.getLogger().Info(useVlc ? "Preferring VLC" : "Will use internal player");
mVideoManager.init(getBufferAmount(), useDeinterlacing);
@@ -506,7 +539,6 @@ public class PlaybackController {
@Override
public void onError(Exception exception) {
mApplication.getLogger().ErrorException("Unable to get internal stream info", exception);
useVlc = true;
mCurrentOptions = vlcOptions;
startItem(item, position, vlcResponse);
}
@@ -550,7 +582,10 @@ public class PlaybackController {
setPlaybackMethod(response.getPlayMethod());
if (useVlc && (!getPlaybackMethod().equals(PlayMethod.Transcode) || isLiveTv)) {
// Force VLC when media is not live TV and the preferred player is VLC
boolean forceVlc = !isLiveTv && mApplication.getPrefs().getString("pref_video_player", "auto").equals("vlc");
if (forceVlc || (useVlc && (!getPlaybackMethod().equals(PlayMethod.Transcode) || isLiveTv))) {
TvApp.getApplication().getLogger().Info("Playing back in VLC.");
mVideoManager.setNativeMode(false);
} else {
@@ -726,7 +761,7 @@ public class PlaybackController {
mVideoManager.disableSubs();
mFragment.showSubLoadingMsg(true);
stream.setDeliveryMethod(SubtitleDeliveryMethod.External);
stream.setDeliveryUrl(String.format("%1$s/Videos/%2$s/%3$s/Subtitles/%4$s/0/Stream.JSON", mApplication.getApiClient().getApiUrl(), mCurrentStreamInfo.getItemId(), mCurrentStreamInfo.getMediaSourceId(), StringHelper.ToStringCultureInvariant(stream.getIndex())));
stream.setDeliveryUrl(String.format("%1$s/Videos/%2$s/%3$s/Subtitles/%4$s/0/Stream.JSON", mApplication.getApiClient().getApiUrl(), mCurrentStreamInfo.getItemId(), mCurrentStreamInfo.getMediaSourceId(), String.valueOf(stream.getIndex())));
mApplication.getApiClient().getSubtitles(stream.getDeliveryUrl(), new Response<SubtitleTrackInfo>() {
@Override
@@ -831,7 +866,7 @@ public class PlaybackController {
public void seek(final long pos) {
mApplication.getLogger().Debug("Seeking to " + pos);
mApplication.getLogger().Debug("Container: "+mCurrentStreamInfo.getContainer());
if (mPlaybackMethod == PlayMethod.Transcode && "mkv".equals(mCurrentStreamInfo.getContainer())) {
if (mPlaybackMethod == PlayMethod.Transcode && ContainerTypes.MKV.equals(mCurrentStreamInfo.getContainer())) {
//mkv transcodes require re-start of stream for seek
mVideoManager.stopPlayback();
mApplication.getPlaybackManager().changeVideoStream(mCurrentStreamInfo, mApplication.getApiClient().getServerInfo().getId(), mCurrentOptions, pos * 10000, mApplication.getApiClient(), new Response<StreamInfo>() {
@@ -849,11 +884,10 @@ public class PlaybackController {
}
});
} else {
if (mVideoManager.isNativeMode() && !isLiveTv && "ts".equals(mCurrentStreamInfo.getContainer())) {
if (mVideoManager.isNativeMode() && !isLiveTv && ContainerTypes.TS.equals(mCurrentStreamInfo.getContainer())) {
//Exo does not support seeking in .ts
Utils.showToast(TvApp.getApplication(), "Unable to seek");
} else
if (mVideoManager.seekTo(pos) >= 0) {
} else if (mVideoManager.seekTo(pos) >= 0) {
if (mFragment != null) {
mFragment.updateEndTime(mVideoManager.getDuration() - pos);
}
@@ -903,7 +937,7 @@ public class PlaybackController {
public void updateTvProgramInfo() {
// Get the current program info when playing a live TV channel
final BaseItemDto channel = getCurrentlyPlayingItem();
if (channel.getType().equals("TvChannel")) {
if (channel.getBaseItemType() == BaseItemType.TvChannel) {
TvApp.getApplication().getApiClient().GetLiveTvChannelAsync(channel.getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<ChannelInfoDto>() {
@Override
public void onResponse(ChannelInfoDto response) {
@@ -945,16 +979,18 @@ public class PlaybackController {
ReportingHelper.reportProgress(getCurrentlyPlayingItem(), getCurrentStreamInfo(), currentTime * 10000, false);
//Do this next up processing here because every 3 seconds is good enough
if (!nextItemReported && hasNextItem() && currentTime >= mNextItemThreshold){
if (!nextItemReported && hasNextItem() && currentTime >= mNextItemThreshold) {
nextItemReported = true;
mFragment.nextItemThresholdHit(getNextItem());
}
}
mApplication.setLastUserInteraction(System.currentTimeMillis());
if (mPlaybackState != PlaybackState.UNDEFINED && mPlaybackState != PlaybackState.IDLE) mHandler.postDelayed(this, REPORT_INTERVAL);
if (mPlaybackState != PlaybackState.UNDEFINED && mPlaybackState != PlaybackState.IDLE) {
mHandler.postDelayed(this, PROGRESS_REPORTING_INTERVAL);
}
}
};
mHandler.postDelayed(mReportLoop, REPORT_INTERVAL);
mHandler.postDelayed(mReportLoop, PROGRESS_REPORTING_INTERVAL);
}
private void startPauseReportLoop() {
@@ -964,13 +1000,15 @@ public class PlaybackController {
public void run() {
long currentTime = isLiveTv ? getTimeShiftedProgress() : mVideoManager.getCurrentPosition();
if (isLiveTv && !directStreamLiveTv) mFragment.setSecondaryTime(getRealTimeProgress());
if (isLiveTv && !directStreamLiveTv) {
mFragment.setSecondaryTime(getRealTimeProgress());
}
ReportingHelper.reportProgress(getCurrentlyPlayingItem(), getCurrentStreamInfo(), currentTime * 10000, true);
mHandler.postDelayed(this, 15000);
mHandler.postDelayed(this, PROGRESS_REPORTING_PAUSE_INTERVAL);
}
};
mHandler.postDelayed(mReportLoop, 15000);
mHandler.postDelayed(mReportLoop, PROGRESS_REPORTING_PAUSE_INTERVAL);
}
private void stopReportLoop() {
@@ -978,7 +1016,6 @@ public class PlaybackController {
mHandler.removeCallbacks(mReportLoop);
}
}
private void delayedSeek(final long position) {
@@ -1081,8 +1118,14 @@ public class PlaybackController {
} else {
if (mDefaultSubIndex >= 0) {
//Default subs requested select them
mApplication.getLogger().Info("Selecting default sub stream: " + mDefaultSubIndex);
switchSubtitleStream(mDefaultSubIndex);
Integer currentIndex = mCurrentOptions.getSubtitleStreamIndex();
if (currentIndex != null && currentIndex == mDefaultSubIndex) {
mApplication.getLogger().Info("Not selecting default subtitle stream because it is already selected");
} else {
mApplication.getLogger().Info("Selecting default sub stream: " + mDefaultSubIndex);
switchSubtitleStream(mDefaultSubIndex);
}
} else {
TvApp.getApplication().getLogger().Info("Turning off subs by default");
mVideoManager.disableSubs();

View File

@@ -1,23 +1,38 @@
package org.jellyfin.androidtv.playback;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.devbrackets.android.exomedia.EMVideoView;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.util.DeviceUtils;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.entities.MediaStream;
import org.jellyfin.apiclient.model.entities.MediaStreamType;
import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
@@ -25,14 +40,7 @@ import org.videolan.libvlc.Media;
import java.util.ArrayList;
import java.util.List;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.entities.MediaStream;
import org.jellyfin.apiclient.model.entities.MediaStreamType;
/**
* Created by Eric on 7/11/2015.
*/
public class VideoManager implements IVLCVout.Callback {
public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
public final static int ZOOM_NORMAL = 0;
public final static int ZOOM_VERTICAL = 1;
@@ -46,7 +54,8 @@ public class VideoManager implements IVLCVout.Callback {
private SurfaceView mSurfaceView;
private SurfaceView mSubtitlesSurface;
private FrameLayout mSurfaceFrame;
private EMVideoView mVideoView;
private SimpleExoPlayer mExoPlayer;
private PlayerView mExoPlayerView;
private LibVLC mLibVLC;
private org.videolan.libvlc.MediaPlayer mVlcPlayer;
private String mCurrentVideoPath;
@@ -66,19 +75,23 @@ public class VideoManager implements IVLCVout.Callback {
private long mForcedTime = -1;
private long mLastTime = -1;
private long mMetaDuration = -1;
private long lastExoPlayerPosition = -1;
private boolean nativeMode = false;
private boolean mSurfaceReady = false;
public boolean isContracted = false;
private boolean hasSubtitlesSurface = false;
private PlaybackListener errorListener;
private PlaybackListener completionListener;
private PlaybackListener preparedListener;
public VideoManager(PlaybackOverlayActivity activity, View view) {
mActivity = activity;
mSurfaceView = (SurfaceView) view.findViewById(R.id.player_surface);
mSurfaceView = view.findViewById(R.id.player_surface);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(mSurfaceCallback);
mSurfaceFrame = (FrameLayout) view.findViewById(R.id.player_surface_frame);
mSubtitlesSurface = (SurfaceView) view.findViewById(R.id.subtitles_surface);
mSurfaceFrame = view.findViewById(R.id.player_surface_frame);
mSubtitlesSurface = view.findViewById(R.id.subtitles_surface);
if (DeviceUtils.is50()) {
mSubtitlesSurface.setZOrderMediaOverlay(true);
mSubtitlesSurface.getHolder().setFormat(PixelFormat.TRANSLUCENT);
@@ -86,8 +99,41 @@ public class VideoManager implements IVLCVout.Callback {
} else {
mSubtitlesSurface.setVisibility(View.GONE);
}
mVideoView = (EMVideoView) view.findViewById(R.id.videoView);
mExoPlayer = ExoPlayerFactory.newSimpleInstance(
TvApp.getApplication(),
new DefaultRenderersFactory(TvApp.getApplication()) {
@Override
protected void buildTextRenderers(Context context,
TextOutput output,
Looper outputLooper, int extensionRendererMode,
ArrayList<Renderer> out) {
// Do not add text renderers since we handle subtitles
}
},
new DefaultTrackSelector());
mExoPlayerView = view.findViewById(R.id.exoPlayerView);
mExoPlayerView.setPlayer(mExoPlayer);
mExoPlayer.addListener(new Player.EventListener() {
@Override
public void onPlayerError(ExoPlaybackException error) {
TvApp.getApplication().getLogger().Error("***** Got error from player");
if (errorListener != null) errorListener.onEvent();
stopProgressLoop();
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
// Do not call listener when paused
if (playbackState == Player.STATE_READY && playWhenReady) {
if (preparedListener != null) preparedListener.onEvent();
startProgressLoop();
} else if (playbackState == Player.STATE_ENDED) {
if (completionListener != null) completionListener.onEvent();
stopProgressLoop();
}
}
});
}
public void init(int buffer, boolean isInterlaced) {
@@ -97,9 +143,9 @@ public class VideoManager implements IVLCVout.Callback {
public void setNativeMode(boolean value) {
nativeMode = value;
if (nativeMode) {
mVideoView.setVisibility(View.VISIBLE);
mExoPlayerView.setVisibility(View.VISIBLE);
} else {
mVideoView.setVisibility(View.GONE);
mExoPlayerView.setVisibility(View.GONE);
}
}
@@ -110,20 +156,20 @@ public class VideoManager implements IVLCVout.Callback {
mZoomMode = mode;
switch (mode) {
case ZOOM_NORMAL:
mVideoView.setScaleY(1);
mVideoView.setScaleX(1);
mExoPlayerView.setScaleY(1);
mExoPlayerView.setScaleX(1);
break;
case ZOOM_VERTICAL:
mVideoView.setScaleX(1);
mVideoView.setScaleY(1.33f);
mExoPlayerView.setScaleX(1);
mExoPlayerView.setScaleY(1.33f);
break;
case ZOOM_HORIZONTAL:
mVideoView.setScaleY(1);
mVideoView.setScaleX(1.33f);
mExoPlayerView.setScaleY(1);
mExoPlayerView.setScaleX(1.33f);
break;
case ZOOM_FULL:
mVideoView.setScaleX(1.33f);
mVideoView.setScaleY(1.33f);
mExoPlayerView.setScaleX(1.33f);
mExoPlayerView.setScaleY(1.33f);
break;
}
@@ -135,14 +181,22 @@ public class VideoManager implements IVLCVout.Callback {
public long getDuration() {
if (nativeMode){
return mVideoView.getDuration() > 0 ? mVideoView.getDuration() : mMetaDuration;
return mExoPlayer.getDuration() > 0 ? mExoPlayer.getDuration() : mMetaDuration;
} else {
return mVlcPlayer.getLength() > 0 ? mVlcPlayer.getLength() : mMetaDuration;
}
}
public long getCurrentPosition() {
if (nativeMode) return mVideoView.getCurrentPosition();
if (nativeMode) {
if (mExoPlayer == null) {
return lastExoPlayerPosition;
} else {
long mExoPlayerCurrentPosition = mExoPlayer.getCurrentPosition();
lastExoPlayerPosition = mExoPlayerCurrentPosition;
return mExoPlayerCurrentPosition;
}
}
if (mVlcPlayer == null) return 0;
@@ -166,17 +220,17 @@ public class VideoManager implements IVLCVout.Callback {
}
public boolean isPlaying() {
return nativeMode ? mVideoView.isPlaying() : mVlcPlayer != null && mVlcPlayer.isPlaying();
return nativeMode ? mExoPlayer.isPlaying() : mVlcPlayer != null && mVlcPlayer.isPlaying();
}
public boolean canSeek() { return nativeMode || mVlcPlayer.isSeekable(); }
public void start() {
if (nativeMode) {
mVideoView.start();
mVideoView.setKeepScreenOn(true);
normalWidth = mVideoView.getLayoutParams().width;
normalHeight = mVideoView.getLayoutParams().height;
mExoPlayer.setPlayWhenReady(true);
mExoPlayerView.setKeepScreenOn(true);
normalWidth = mExoPlayerView.getLayoutParams().width;
normalHeight = mExoPlayerView.getLayoutParams().height;
} else {
if (!mSurfaceReady) {
TvApp.getApplication().getLogger().Error("Attempt to play before surface ready");
@@ -192,8 +246,8 @@ public class VideoManager implements IVLCVout.Callback {
public void play() {
if (nativeMode) {
mVideoView.start();
mVideoView.setKeepScreenOn(true);
mExoPlayer.setPlayWhenReady(true);
mExoPlayerView.setKeepScreenOn(true);
} else {
mVlcPlayer.play();
mSurfaceView.setKeepScreenOn(true);
@@ -202,8 +256,8 @@ public class VideoManager implements IVLCVout.Callback {
public void pause() {
if (nativeMode) {
mVideoView.pause();
mVideoView.setKeepScreenOn(false);
mExoPlayer.setPlayWhenReady(false);
mExoPlayerView.setKeepScreenOn(false);
} else {
mVlcPlayer.pause();
mSurfaceView.setKeepScreenOn(false);
@@ -217,7 +271,7 @@ public class VideoManager implements IVLCVout.Callback {
public void stopPlayback() {
if (nativeMode) {
mVideoView.stopPlayback();
mExoPlayer.stop();
} else {
mVlcPlayer.stop();
}
@@ -227,8 +281,8 @@ public class VideoManager implements IVLCVout.Callback {
public long seekTo(long pos) {
if (nativeMode) {
Long intPos = pos;
TvApp.getApplication().getLogger().Info("Exo length in seek is: " + mVideoView.getDuration());
mVideoView.seekTo(intPos.intValue());
TvApp.getApplication().getLogger().Info("Exo length in seek is: " + mExoPlayer.getDuration());
mExoPlayer.seekTo(intPos.intValue());
return pos;
} else {
if (mVlcPlayer == null || !mVlcPlayer.isSeekable()) return -1;
@@ -259,7 +313,9 @@ public class VideoManager implements IVLCVout.Callback {
if (nativeMode) {
try {
mVideoView.setVideoPath(path);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(TvApp.getApplication(), "ATV/ExoPlayer");
mExoPlayer.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(path)));
} catch (IllegalStateException e) {
TvApp.getApplication().getLogger().ErrorException("Unable to set video path. Probably backing out.", e);
}
@@ -277,7 +333,7 @@ public class VideoManager implements IVLCVout.Callback {
public void hideSurface() {
if (nativeMode) {
mVideoView.setVisibility(View.INVISIBLE);
mExoPlayerView.setVisibility(View.INVISIBLE);
} else {
mSurfaceView.setVisibility(View.INVISIBLE);
}
@@ -285,7 +341,7 @@ public class VideoManager implements IVLCVout.Callback {
public void showSurface() {
if (nativeMode) {
mVideoView.setVisibility(View.VISIBLE);
mExoPlayerView.setVisibility(View.VISIBLE);
} else {
mSurfaceView.setVisibility(View.VISIBLE);
}
@@ -431,12 +487,12 @@ public class VideoManager implements IVLCVout.Callback {
options.add("--network-caching=" + buffer);
options.add("--no-audio-time-stretch");
options.add("--avcodec-skiploopfilter");
options.add("" + 1);
options.add("1");
options.add("--avcodec-skip-frame");
options.add("0");
options.add("--avcodec-skip-idct");
options.add("0");
options.add("--androidwindow-chroma");
options.add("--android-display-chroma");
options.add("RV32");
options.add("--audio-resampler");
options.add("soxr");
@@ -449,18 +505,8 @@ public class VideoManager implements IVLCVout.Callback {
// options.add("Universal (UTF-8)");
options.add("-v");
mLibVLC = new LibVLC(options);
mLibVLC = new LibVLC(TvApp.getApplication(), options);
TvApp.getApplication().getLogger().Info("Network buffer set to " + buffer);
LibVLC.setOnNativeCrashListener(new LibVLC.OnNativeCrashListener() {
@Override
public void onNativeCrash() {
new Exception().printStackTrace();
//todo custom error reporter
mActivity.finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
});
mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
mVlcPlayer.setAudioOutput(Utils.downMixAudio() ? "opensles_android" : "android_audiotrack");
@@ -474,12 +520,9 @@ public class VideoManager implements IVLCVout.Callback {
mVlcPlayer.getVLCVout().detachViews();
mVlcPlayer.getVLCVout().setVideoView(mSurfaceView);
if (hasSubtitlesSurface) mVlcPlayer.getVLCVout().setSubtitlesView(mSubtitlesSurface);
mVlcPlayer.getVLCVout().attachViews();
mVlcPlayer.getVLCVout().attachViews(this);
TvApp.getApplication().getLogger().Debug("Surface attached");
mSurfaceReady = true;
mVlcPlayer.getVLCVout().addCallback(this);
} catch (Exception e) {
TvApp.getApplication().getLogger().ErrorException("Error creating VLC player", e);
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.msg_video_playback_error));
@@ -487,23 +530,30 @@ public class VideoManager implements IVLCVout.Callback {
}
private void releasePlayer() {
if (mVlcPlayer == null) return;
if (mVlcPlayer != null) {
mVlcPlayer.setEventListener(null);
mVlcPlayer.stop();
mVlcPlayer.getVLCVout().detachViews();
mVlcPlayer.release();
mLibVLC.release();
mLibVLC = null;
mVlcPlayer = null;
}
if (mExoPlayer != null) {
mExoPlayerView.setPlayer(null);
mExoPlayer.release();
mExoPlayer = null;
}
mVlcPlayer.setEventListener(null);
mVlcPlayer.stop();
mVlcPlayer.getVLCVout().detachViews();
mVlcPlayer.release();
mLibVLC = null;
mVlcPlayer = null;
mSurfaceView.setKeepScreenOn(false);
}
int normalWidth;
int normalHeight;
public void contractVideo(int height) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) (nativeMode ? mVideoView.getLayoutParams() : mSurfaceView.getLayoutParams());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) (nativeMode ? mExoPlayerView.getLayoutParams() : mSurfaceView.getLayoutParams());
if (isContracted) return;
Activity activity = TvApp.getApplication().getCurrentActivity();
@@ -516,8 +566,8 @@ public class VideoManager implements IVLCVout.Callback {
lp.bottomMargin = ((lp.height - normalHeight) / 2) - 50;
if (nativeMode) {
mVideoView.setLayoutParams(lp);
mVideoView.invalidate();
mExoPlayerView.setLayoutParams(lp);
mExoPlayerView.invalidate();
} else mSurfaceView.setLayoutParams(lp);
isContracted = true;
@@ -526,14 +576,14 @@ public class VideoManager implements IVLCVout.Callback {
public void setVideoFullSize() {
if (normalHeight == 0) return;
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) (nativeMode ? mVideoView.getLayoutParams() : mSurfaceView.getLayoutParams());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) (nativeMode ? mExoPlayerView.getLayoutParams() : mSurfaceView.getLayoutParams());
lp.height = normalHeight;
lp.width = normalWidth;
if (nativeMode) {
lp.rightMargin = 0;
lp.bottomMargin = 0;
mVideoView.setLayoutParams(lp);
mVideoView.invalidate();
mExoPlayerView.setLayoutParams(lp);
mExoPlayerView.invalidate();
} else mSurfaceView.setLayoutParams(lp);
isContracted = false;
@@ -609,19 +659,7 @@ public class VideoManager implements IVLCVout.Callback {
if (hasSubtitlesSurface) mSubtitlesSurface.invalidate();
}
PlaybackListener errorListener;
public void setOnErrorListener(final PlaybackListener listener) {
mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
TvApp.getApplication().getLogger().Error("***** Got error from player");
listener.onEvent();
stopProgressLoop();
return true;
}
});
mVlcHandler.setOnErrorListener(listener);
errorListener = listener;
}
@@ -631,28 +669,12 @@ public class VideoManager implements IVLCVout.Callback {
}
public void setOnCompletionListener(final PlaybackListener listener) {
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
listener.onEvent();
stopProgressLoop();
}
});
completionListener = listener;
mVlcHandler.setOnCompletionListener(listener);
}
private MediaPlayer mNativeMediaPlayer;
public void setOnPreparedListener(final PlaybackListener listener) {
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mNativeMediaPlayer = mp;
listener.onEvent();
startProgressLoop();
}
});
preparedListener = listener;
mVlcHandler.setOnPreparedListener(listener);
}
@@ -701,7 +723,7 @@ public class VideoManager implements IVLCVout.Callback {
};
@Override
public void onNewLayout(IVLCVout vout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) {
public void onNewVideoLayout(IVLCVout vout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) {
if (width * height == 0 || isContracted)
return;
@@ -719,23 +741,6 @@ public class VideoManager implements IVLCVout.Callback {
changeSurfaceLayout(mVideoWidth, mVideoHeight, mVideoVisibleWidth, mVideoVisibleHeight, mSarNum, mSarDen);
}
});
}
@Override
public void onHardwareAccelerationError(IVLCVout ivlcVout) {
TvApp.getApplication().getLogger().Error("VLC Hardware acceleration error");
TvApp.getApplication().getPlaybackController().playerErrorEncountered();
}
@Override
public void onSurfacesCreated(IVLCVout ivlcVout) {
}
@Override
public void onSurfacesDestroyed(IVLCVout ivlcVout) {
}
public Integer translateVlcAudioId(Integer vlcId) {

View File

@@ -3,10 +3,11 @@ package org.jellyfin.androidtv.presentation;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.v17.leanback.widget.BaseCardView;
import android.support.v17.leanback.widget.Presenter;
import android.support.v7.graphics.Palette;
import androidx.leanback.widget.BaseCardView;
import androidx.leanback.widget.Presenter;
import androidx.palette.graphics.Palette;
import android.view.View;
import android.view.ViewGroup;
@@ -64,17 +65,17 @@ public class CardPresenter extends Presenter {
static class ViewHolder extends Presenter.ViewHolder {
private int cardWidth = 230;
private int cardHeight = 280;
private BaseRowItem mItem;
private MyImageCardView mCardView;
private Drawable mDefaultCardImage;
public ViewHolder(View view) {
super(view);
mCardView = (MyImageCardView) view;
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.video);
mCardView = (MyImageCardView) view;
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_video);
}
public int getCardHeight() {
@@ -93,95 +94,113 @@ public class CardPresenter extends Presenter {
BaseItemDto itemDto = mItem.getBaseItem();
boolean showWatched = true;
boolean showProgress = false;
double aspect = imageType.equals(ImageType.BANNER) ? ASPECT_RATIO_BANNER : imageType.equals(ImageType.THUMB) ? ImageUtils.ASPECT_RATIO_16_9 : Utils.getSafeValue(ImageUtils.getImageAspectRatio(itemDto, m.getPreferParentThumb()), ImageUtils.ASPECT_RATIO_7_9);
switch (itemDto.getType()) {
case "Audio":
case "MusicAlbum":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.audio);
if (aspect < 0.8) aspect = 1.0;
double aspect;
if (imageType.equals(ImageType.BANNER)) {
aspect = ASPECT_RATIO_BANNER;
} else if (imageType.equals(ImageType.THUMB)) {
aspect = ImageUtils.ASPECT_RATIO_16_9;
} else {
aspect = Utils.getSafeValue(ImageUtils.getImageAspectRatio(itemDto, m.getPreferParentThumb()), ImageUtils.ASPECT_RATIO_7_9);
}
switch (itemDto.getBaseItemType()) {
case Audio:
case MusicAlbum:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_audio);
if (aspect < 0.8) {
aspect = 1.0;
}
showWatched = false;
break;
case "Person":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.person);
case Person:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_person);
break;
case "MusicArtist":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.person);
if (aspect <.8) aspect = 1.0;
case MusicArtist:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_person);
if (aspect < .8) {
aspect = 1.0;
}
showWatched = false;
break;
case "RecordingGroup":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.recgroup);
case RecordingGroup:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_record);
break;
case "Season":
case "Series":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tv);
case Season:
case Series:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_tv);
break;
case "Episode":
case Episode:
//TvApp.getApplication().getLogger().Debug("**** Image width: "+ cardWidth + " Aspect: " + Utils.getImageAspectRatio(itemDto, m.getPreferParentThumb()) + " Item: "+itemDto.getName());
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tvl);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_land_tv);
switch (itemDto.getLocationType()) {
case FileSystem:
break;
case Remote:
break;
case Virtual:
mCardView.setBanner((itemDto.getPremiereDate() != null ? TimeUtils.convertToLocalDate(itemDto.getPremiereDate()) : new Date(System.currentTimeMillis()+1)).getTime() > System.currentTimeMillis() ? R.drawable.futurebanner : R.drawable.missingbanner);
mCardView.setBanner((itemDto.getPremiereDate() != null ? TimeUtils.convertToLocalDate(itemDto.getPremiereDate()) : new Date(System.currentTimeMillis() + 1)).getTime() > System.currentTimeMillis() ? R.drawable.banner_edge_future : R.drawable.banner_edge_missing);
break;
case Offline:
mCardView.setBanner(R.drawable.offlinebanner);
mCardView.setBanner(R.drawable.banner_edge_offline);
break;
}
showProgress = true;
//Always show info for episodes
mCardView.setCardType(BaseCardView.CARD_TYPE_INFO_UNDER);
break;
case "CollectionFolder":
case "Folder":
case "MovieGenreFolder":
case "MusicGenreFolder":
case "MovieGenre":
case "Genre":
case "MusicGenre":
case "UserView":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.folder);
case CollectionFolder:
// Force the aspect ratio to 16x9 because the server is returning the wrong value of 1
aspect = ImageUtils.ASPECT_RATIO_16_9;
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_folder);
break;
case "Photo":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.photo);
case Folder:
case MovieGenreFolder:
case MusicGenreFolder:
case MovieGenre:
case Genre:
case MusicGenre:
case UserView:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_folder);
break;
case Photo:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_land_photo);
showWatched = false;
break;
case "PhotoAlbum":
case "Playlist":
case PhotoAlbum:
case Playlist:
showWatched = false;
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.folder);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_folder);
break;
case "Movie":
case "Video":
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.video);
case Movie:
case Video:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_video);
showProgress = true;
break;
default:
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.video);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_video);
break;
}
cardHeight = !m.isStaticHeight() ? (aspect > 1 ? lHeight : pHeight) : sHeight;
cardWidth = (int)(aspect * cardHeight);
if (cardWidth < 10) cardWidth = 230; //Guard against zero size images causing picasso to barf
if (itemDto.getLocationType() == LocationType.Offline) mCardView.setBanner(R.drawable.offlinebanner);
if (itemDto.getIsPlaceHolder() != null && itemDto.getIsPlaceHolder()) mCardView.setBanner(R.drawable.externaldiscbanner);
cardWidth = (int) (aspect * cardHeight);
if (cardWidth < 10) {
cardWidth = 230; //Guard against zero size images causing picasso to barf
}
if (itemDto.getLocationType() == LocationType.Offline) {
mCardView.setBanner(R.drawable.banner_edge_offline);
}
if (itemDto.getIsPlaceHolder() != null && itemDto.getIsPlaceHolder()) {
mCardView.setBanner(R.drawable.banner_edge_disc);
}
UserItemDataDto userData = itemDto.getUserData();
if (showWatched && userData != null) {
if (userData.getPlayed()) {
mCardView.setUnwatchedCount(0);
} else {
if (userData.getUnplayedItemCount() != null) {
mCardView.setUnwatchedCount(userData.getUnplayedItemCount());
}
} else if (userData.getUnplayedItemCount() != null) {
mCardView.setUnwatchedCount(userData.getUnplayedItemCount());
}
}
if(showProgress && itemDto.getRunTimeTicks() != null && itemDto.getRunTimeTicks() > 0 && userData != null && userData.getPlaybackPositionTicks() > 0) {
mCardView.setProgress(((int)(userData.getPlaybackPositionTicks() * 100.0 / itemDto.getRunTimeTicks()))); // force floating pt math with 100.0
if (showProgress && itemDto.getRunTimeTicks() != null && itemDto.getRunTimeTicks() > 0 && userData != null && userData.getPlaybackPositionTicks() > 0) {
mCardView.setProgress(((int) (userData.getPlaybackPositionTicks() * 100.0 / itemDto.getRunTimeTicks()))); // force floating pt math with 100.0
} else {
mCardView.setProgress(0);
}
@@ -191,103 +210,109 @@ public class CardPresenter extends Presenter {
ChannelInfoDto channel = mItem.getChannelInfo();
double tvAspect = imageType.equals(ImageType.BANNER) ? ASPECT_RATIO_BANNER : imageType.equals(ImageType.THUMB) ? ImageUtils.ASPECT_RATIO_16_9 : Utils.getSafeValue(channel.getPrimaryImageAspectRatio(), ImageUtils.ASPECT_RATIO_7_9);
cardHeight = !m.isStaticHeight() ? tvAspect > 1 ? lHeight : pHeight : sHeight;
cardWidth = (int)((tvAspect) * cardHeight);
if (cardWidth < 10) cardWidth = 230; //Guard against zero size images causing picasso to barf
cardWidth = (int) ((tvAspect) * cardHeight);
if (cardWidth < 10) {
cardWidth = 230; //Guard against zero size images causing picasso to barf
}
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tv);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_tv);
break;
case LiveTvProgram:
BaseItemDto program = mItem.getProgramInfo();
Double programAspect = program.getPrimaryImageAspectRatio();
if (programAspect == null) programAspect = Utils.isTrue(program.getIsMovie()) ? ImageUtils.ASPECT_RATIO_2_3 : ImageUtils.ASPECT_RATIO_16_9;
if (Utils.isTrue(program.getIsMovie())) {
// The server reports the incorrect image aspect ratio for movies, so we are overriding it here
programAspect = ImageUtils.ASPECT_RATIO_2_3;
} else if (programAspect == null) {
programAspect = ImageUtils.ASPECT_RATIO_16_9;
}
cardHeight = !m.isStaticHeight() ? programAspect > 1 ? lHeight : pHeight : sHeight;
cardWidth = (int)((programAspect) * cardHeight);
if (cardWidth < 10) cardWidth = 230; //Guard against zero size images causing picasso to barf
cardWidth = (int) ((programAspect) * cardHeight);
if (cardWidth < 10) {
cardWidth = 230; //Guard against zero size images causing picasso to barf
}
switch (program.getLocationType()) {
case FileSystem:
break;
case Remote:
case Offline:
break;
case Virtual:
if (program.getStartDate() != null && TimeUtils.convertToLocalDate(program.getStartDate()).getTime() > System.currentTimeMillis()) mCardView.setBanner(R.drawable.futurebanner);
break;
case Offline:
if (program.getStartDate() != null && TimeUtils.convertToLocalDate(program.getStartDate()).getTime() > System.currentTimeMillis()) {
mCardView.setBanner(R.drawable.banner_edge_future);
}
break;
}
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tvl);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_land_tv);
//Always show info for programs
mCardView.setCardType(BaseCardView.CARD_TYPE_INFO_UNDER);
break;
case LiveTvRecording:
BaseItemDto recording = mItem.getRecordingInfo();
double recordingAspect = imageType.equals(ImageType.BANNER) ? ASPECT_RATIO_BANNER : (imageType.equals(ImageType.THUMB) ? ImageUtils.ASPECT_RATIO_16_9 : Utils.getSafeValue(recording.getPrimaryImageAspectRatio(), ImageUtils.ASPECT_RATIO_7_9));
cardHeight = !m.isStaticHeight() ? recordingAspect > 1 ? lHeight : pHeight : sHeight;
cardWidth = (int)((recordingAspect) * cardHeight);
if (cardWidth < 10) cardWidth = 230; //Guard against zero size images causing picasso to barf
cardWidth = (int) ((recordingAspect) * cardHeight);
if (cardWidth < 10) {
cardWidth = 230; //Guard against zero size images causing picasso to barf
}
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tv);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_tv);
break;
case Server:
cardWidth = (int)(ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.server);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_server);
break;
case Person:
cardHeight = !m.isStaticHeight() ? pHeight : sHeight;
cardWidth = (int)(ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.person);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_person);
break;
case User:
cardWidth = (int)(ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.person);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_person);
break;
case Chapter:
cardHeight = !m.isStaticHeight() ? pHeight : sHeight;
cardWidth = (int)(ImageUtils.ASPECT_RATIO_16_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_16_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.chaptertile);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_chapter);
break;
case SearchHint:
switch (mItem.getSearchHint().getType()) {
case "Episode":
cardWidth = (int)(ImageUtils.ASPECT_RATIO_16_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_16_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tv);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_tv);
break;
case "Person":
cardWidth = (int)(ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.person);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_person);
break;
default:
cardWidth = (int)(ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.video);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_video);
break;
}
break;
case GridButton:
cardHeight = !m.isStaticHeight() ? pHeight : sHeight;
cardWidth = (int)(ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_7_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.video);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_port_video);
break;
case SeriesTimer:
cardHeight = !m.isStaticHeight() ? pHeight : sHeight;
cardWidth = (int)(ImageUtils.ASPECT_RATIO_16_9 * cardHeight);
cardWidth = (int) (ImageUtils.ASPECT_RATIO_16_9 * cardHeight);
mCardView.setMainImageDimensions(cardWidth, cardHeight);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.seriestimer);
mDefaultCardImage = TvApp.getApplication().getDrawableCompat(R.drawable.tile_land_series_timer);
//Always show info for timers
mCardView.setCardType(BaseCardView.CARD_TYPE_INFO_UNDER);
break;
}
}
@@ -304,19 +329,18 @@ public class CardPresenter extends Presenter {
}
protected void updateCardViewImage(String url) {
if (!validContext()) return;
if (!validContext()) {
return;
}
try {
if (url == null) {
//TvApp.getApplication().getLogger().Debug("Clearing card image");
Glide.with(getContext())
.load("nothing")
.fitCenter()
.error(mDefaultCardImage)
.into(mCardView.getMainImageView());
} else {
//TvApp.getApplication().getLogger().Debug("Loading card image");
Glide.with(getContext())
.load(url)
.asBitmap()
@@ -330,7 +354,6 @@ public class CardPresenter extends Presenter {
}
});
}
} catch (IllegalArgumentException e) {
TvApp.getApplication().getLogger().Info("Image load aborted due to activity closing");
}
@@ -340,25 +363,23 @@ public class CardPresenter extends Presenter {
mCardView.clearBanner();
mCardView.setUnwatchedCount(-1);
mCardView.setProgress(0);
if (!validContext()) return;
//TvApp.getApplication().getLogger().Debug("Resetting card image");
if (!validContext()) {
return;
}
try {
Glide.with(getContext())
.load(Uri.parse("android.resource://org.jellyfin.androidtv/drawable/loading"))
.load(TvApp.getApplication().getDrawable(R.drawable.loading))
.fitCenter()
.error(mDefaultCardImage)
.into(mCardView.getMainImageView());
} catch (IllegalArgumentException e) {
TvApp.getApplication().getLogger().Info("Image reset aborted due to activity closing");
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
//Log.d(TAG, "onCreateViewHolder");
mViewParent = parent;
MyImageCardView cardView = new MyImageCardView(getContext(), mShowInfo);
@@ -370,17 +391,22 @@ public class CardPresenter extends Presenter {
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
if (!(item instanceof BaseRowItem)) return;
if (!(item instanceof BaseRowItem)) {
return;
}
BaseRowItem rowItem = (BaseRowItem) item;
if (!rowItem.isValid()) return;
if (!rowItem.isValid()) {
return;
}
ViewHolder holder = (ViewHolder) viewHolder;
holder.setItem(rowItem, mImageType, 260, 300, mStaticHeight);
holder.mCardView.setTitleText(rowItem.getCardName());
holder.mCardView.setContentText(rowItem.getSubText());
if (ImageType.DEFAULT.equals(mImageType)) holder.mCardView.setOverlayInfo(rowItem);
if (ImageType.DEFAULT.equals(mImageType)) {
holder.mCardView.setOverlayInfo(rowItem);
}
holder.mCardView.showFavIcon(rowItem.isFavorite());
if (rowItem.isPlaying()) {
holder.mCardView.setPlayingIndicator(true);
@@ -389,24 +415,19 @@ public class CardPresenter extends Presenter {
Drawable badge = rowItem.getBadgeImage();
if (badge != null) {
((ViewHolder) viewHolder).mCardView.setBadgeImage(badge);
}
}
((ViewHolder) viewHolder).updateCardViewImage(rowItem.getImageUrl(mImageType, ((ViewHolder) viewHolder).getCardHeight()));
}
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
//TvApp.getApplication().getLogger().Debug("onUnbindViewHolder");
//Get the image out of there so won't be there if recycled
((ViewHolder) viewHolder).resetCardViewImage();
}
@Override
public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
//Log.d(TAG, "onViewAttachedToWindow");
}
}

View File

@@ -1,7 +1,7 @@
package org.jellyfin.androidtv.presentation;
import android.content.Context;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.Presenter;
import android.view.View;
import android.view.ViewGroup;

View File

@@ -1,8 +1,8 @@
package org.jellyfin.androidtv.presentation;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.ListRowPresenter;
import androidx.leanback.widget.RowPresenter;
import android.view.View;
/**

View File

@@ -2,7 +2,7 @@ package org.jellyfin.androidtv.presentation;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.Presenter;
import android.view.View;
import android.view.ViewGroup;
@@ -19,7 +19,9 @@ public class GridButtonPresenter extends Presenter {
private int mCardHeight = 220;
private static ViewGroup mViewParent;
public GridButtonPresenter() { super();}
public GridButtonPresenter() {
super();
}
public GridButtonPresenter(boolean showinfo, int width, int height) {
this();
@@ -41,9 +43,9 @@ public class GridButtonPresenter extends Presenter {
public ViewHolder(View view) {
super(view);
mCardView = (MyImageCardView) view;
mDefaultCardImage = TvApp.getApplication().getResources().getDrawable(R.drawable.gears);
mCardView = (MyImageCardView) view;
mDefaultCardImage = TvApp.getApplication().getResources().getDrawable(R.drawable.tile_settings);
}
public void setItem(GridButton m, int width, int height) {
@@ -73,7 +75,6 @@ public class GridButtonPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
//Log.d(TAG, "onCreateViewHolder");
mViewParent = parent;
MyImageCardView cardView = new MyImageCardView(getContext(), mShowInfo);
@@ -91,22 +92,16 @@ public class GridButtonPresenter extends Presenter {
ViewHolder vh = (ViewHolder) viewHolder;
vh.setItem(gridItem, mCardWidth, mCardHeight);
//Log.d(TAG, "onBindViewHolder");
vh.mCardView.setTitleText(gridItem.getText());
vh.mCardView.setOverlayText(gridItem.getText());
vh.updateCardViewImage(gridItem.getImageIndex());
}
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
//Log.d(TAG, "onUnbindViewHolder");
}
@Override
public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
//Log.d(TAG, "onViewAttachedToWindow");
}
}

View File

@@ -4,16 +4,18 @@ package org.jellyfin.androidtv.presentation;
* Created by Eric on 8/17/2015.
*/
import android.support.v17.leanback.widget.FocusHighlight;
import android.support.v17.leanback.widget.FocusHighlightHelper;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnChildSelectedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.ShadowOverlayContainer;
import androidx.leanback.widget.FocusHighlight;
import androidx.leanback.widget.FocusHighlightHelper;
import androidx.leanback.widget.HorizontalGridView;
import androidx.leanback.widget.ItemBridgeAdapter;
import androidx.leanback.widget.ObjectAdapter;
import androidx.leanback.widget.OnChildViewHolderSelectedListener;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.ShadowOverlayContainer;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -187,7 +189,7 @@ public class HorizontalGridPresenter extends Presenter {
vh.getGridView().setNumRows(mNumRows);
vh.getGridView().setFadingRightEdge(true);
vh.getGridView().setFadingRightEdgeLength(100);
vh.getGridView().setItemMargin(10);
vh.getGridView().setItemSpacing(10);
vh.mInitialized = true;
vh.mItemBridgeAdapter.setWrapper(mWrapper);
@@ -200,10 +202,10 @@ public class HorizontalGridPresenter extends Presenter {
mZoomFactor, true);
final ViewHolder gridViewHolder = vh;
vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() {
vh.getGridView().setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
@Override
public void onChildSelected(ViewGroup parent, View view, int position, long id) {
selectChildView(gridViewHolder, view);
public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) {
selectChildView(gridViewHolder, child.itemView);
}
});

View File

@@ -1,7 +1,7 @@
package org.jellyfin.androidtv.presentation;
import android.content.Context;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.Presenter;
import android.view.View;
import android.view.ViewGroup;

View File

@@ -2,7 +2,7 @@ package org.jellyfin.androidtv.presentation;
import android.app.Activity;
import android.graphics.Paint;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.Presenter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,8 +36,8 @@ public abstract class MyAbstractDetailsPresenter extends Presenter {
public ViewHolder(View view) {
super(view);
mTitle = (TextView) view.findViewById(android.support.v17.leanback.R.id.lb_details_description_title);
mSubtitle = (TextView) view.findViewById(android.support.v17.leanback.R.id.lb_details_description_subtitle);
mTitle = (TextView) view.findViewById(androidx.leanback.R.id.lb_details_description_title);
mSubtitle = (TextView) view.findViewById(androidx.leanback.R.id.lb_details_description_subtitle);
mActivity = (Activity) view.getContext();
mInfoRow = new LinearLayout(mActivity);
LinearLayout.LayoutParams layout = new LinearLayout.LayoutParams(Utils.convertDpToPixel(mActivity, 500), Utils.convertDpToPixel(mActivity,20));
@@ -48,23 +48,23 @@ public abstract class MyAbstractDetailsPresenter extends Presenter {
int index = parent.indexOfChild(mSubtitle);
parent.removeView(mSubtitle);
parent.addView(mInfoRow, index);
mBody = (TextView) view.findViewById(android.support.v17.leanback.R.id.lb_details_description_body);
mBody = (TextView) view.findViewById(androidx.leanback.R.id.lb_details_description_body);
Paint.FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
final int titleAscent = view.getResources().getDimensionPixelSize(
android.support.v17.leanback.R.dimen.lb_details_description_title_baseline);
androidx.leanback.R.dimen.lb_details_description_title_baseline);
// Ascent is negative
mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize(
android.support.v17.leanback.R.dimen.lb_details_description_under_title_baseline_margin);
androidx.leanback.R.dimen.lb_details_description_under_title_baseline_margin);
mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize(
android.support.v17.leanback.R.dimen.lb_details_description_under_subtitle_baseline_margin);
androidx.leanback.R.dimen.lb_details_description_under_subtitle_baseline_margin);
mTitleLineSpacing = view.getResources().getDimensionPixelSize(
android.support.v17.leanback.R.dimen.lb_details_description_title_line_spacing);
androidx.leanback.R.dimen.lb_details_description_title_line_spacing);
mBodyLineSpacing = view.getResources().getDimensionPixelSize(
android.support.v17.leanback.R.dimen.lb_details_description_body_line_spacing);
androidx.leanback.R.dimen.lb_details_description_body_line_spacing);
mBodyMaxLines = 6;
mBodyMinLines = 4;
@@ -109,7 +109,7 @@ public abstract class MyAbstractDetailsPresenter extends Presenter {
@Override
public final ViewHolder onCreateViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(android.support.v17.leanback.R.layout.lb_details_description, parent, false);
.inflate(androidx.leanback.R.layout.lb_details_description, parent, false);
return new ViewHolder(v);
}

View File

@@ -2,7 +2,11 @@ package org.jellyfin.androidtv.presentation;
import android.graphics.Color;
import android.graphics.Typeface;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.RowPresenter;
import android.os.Build;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -112,10 +116,23 @@ public class MyDetailsOverviewRowPresenter extends RowPresenter {
vh.mPoster.setImageDrawable(row.getImageDrawable());
//vh.mStudioImage.setImageDrawable(row.getStudioDrawable());
vh.mSummary.setText(row.getSummary());
//vh.mSummaryTitle.setText(row.getSummaryTitle());
switch (row.getItem().getType()) {
case "Person":
// Support simple HTML elements
String summaryRaw = row.getSummary();
if (summaryRaw != null) {
Spanned summarySpanned;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
summarySpanned = Html.fromHtml(summaryRaw, Html.FROM_HTML_MODE_COMPACT);
} else {
summarySpanned = Html.fromHtml(summaryRaw);
}
vh.mSummary.setText(summarySpanned);
}
switch (row.getItem().getBaseItemType()) {
case Person:
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) vh.mSummary.getLayoutParams();
params.topMargin = 10;
params.height = Utils.convertDpToPixel(TvApp.getApplication(), 185);
@@ -143,7 +160,7 @@ public class MyDetailsOverviewRowPresenter extends RowPresenter {
for (String genre : item.getGenres()) {
if (!first) InfoLayoutHelper.addSpacer(TvApp.getApplication().getCurrentActivity(), layout, " / ", 12);
first = false;
layout.addView(new GenreButton(TvApp.getApplication().getCurrentActivity(), TvApp.getApplication().getDefaultFont(), 14, genre, item.getType()));
layout.addView(new GenreButton(TvApp.getApplication().getCurrentActivity(), TvApp.getApplication().getDefaultFont(), 14, genre, item.getBaseItemType()));
}
}
}

View File

@@ -7,9 +7,8 @@ package org.jellyfin.androidtv.presentation;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.BaseCardView;
import androidx.leanback.widget.BaseCardView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -126,9 +125,9 @@ public class MyImageCardView extends BaseCardView {
public void setPlayingIndicator(boolean playing) {
if (playing) {
mBadgeImage.setBackgroundResource(R.drawable.eq_animation);
// TODO use decent animation for equalizer icon
mBadgeImage.setBackgroundResource(R.drawable.ic_play);
mBadgeImage.setVisibility(VISIBLE);
((AnimationDrawable)mBadgeImage.getBackground()).start();
} else {
mBadgeImage.setBackgroundResource(R.drawable.blank10x10);
}
@@ -238,28 +237,28 @@ public class MyImageCardView extends BaseCardView {
if (mOverlayName == null) return;
if (getCardType() == BaseCardView.CARD_TYPE_MAIN_ONLY && item.showCardInfoOverlay()) {
switch (item.getType()) {
case "Photo":
switch (item.getBaseItemType()) {
case Photo:
mOverlayName.setText(item.getBaseItem().getPremiereDate() != null ? android.text.format.DateFormat.getDateFormat(TvApp.getApplication()).format(TimeUtils.convertToLocalDate(item.getBaseItem().getPremiereDate())) : item.getFullName());
mOverlayIcon.setImageResource(R.drawable.camera);
mOverlayIcon.setImageResource(R.drawable.ic_camera);
break;
case "PhotoAlbum":
case PhotoAlbum:
mOverlayName.setText(item.getFullName());
mOverlayIcon.setImageResource(R.drawable.photoalbum);
mOverlayIcon.setImageResource(R.drawable.ic_photos);
break;
case "Video":
case Video:
mOverlayName.setText(item.getFullName());
mOverlayIcon.setImageResource(R.drawable.film);
mOverlayIcon.setImageResource(R.drawable.ic_movie);
break;
case "Playlist":
case "MusicArtist":
case "Person":
case Playlist:
case MusicArtist:
case Person:
mOverlayName.setText(item.getFullName());
hideIcon();
break;
default:
mOverlayName.setText(item.getFullName());
mOverlayIcon.setImageResource(item.isFolder() ? R.drawable.foldersmall : R.drawable.blank30x30);
mOverlayIcon.setImageResource(item.isFolder() ? R.drawable.ic_folder : R.drawable.blank30x30);
break;
}
mOverlayCount.setText(item.getChildCountStr());
@@ -389,6 +388,4 @@ public class MyImageCardView extends BaseCardView {
mImageView.setAlpha(1f);
super.onDetachedFromWindow();
}
}

View File

@@ -1,7 +1,7 @@
package org.jellyfin.androidtv.presentation;
import android.graphics.Bitmap;
import android.support.v17.leanback.app.BackgroundManager;
import androidx.leanback.app.BackgroundManager;
import com.squareup.picasso.Picasso;

View File

@@ -1,8 +1,8 @@
package org.jellyfin.androidtv.presentation;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.ListRowPresenter;
import androidx.leanback.widget.RowPresenter;
import org.jellyfin.androidtv.TvApp;

View File

@@ -1,7 +1,7 @@
package org.jellyfin.androidtv.presentation;
import android.graphics.Color;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.Presenter;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.TextView;

View File

@@ -1,88 +1,90 @@
package org.jellyfin.androidtv.presentation;
import android.media.MediaPlayer;
import android.os.Handler;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.base.BaseActivity;
import org.jellyfin.androidtv.util.Utils;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* Created by Eric on 10/24/2015.
*/
public class ThemeManager {
private static boolean isEnabled() {
return TvApp.getApplication().getPrefs().getBoolean("pref_enable_themes",true);
return TvApp.getApplication().getPrefs().getBoolean("pref_enable_themes", true);
}
private static boolean isHalloween() {
if (!isEnabled()) return false;
if (!isEnabled()) {
return false;
}
Calendar today = new GregorianCalendar();
return today.get(Calendar.MONTH) == 9 && (today.get(Calendar.DAY_OF_MONTH) == 30 || today.get(Calendar.DAY_OF_MONTH) == 31);
return today.get(Calendar.MONTH) == Calendar.OCTOBER &&
(today.get(Calendar.DAY_OF_MONTH) == 30 || today.get(Calendar.DAY_OF_MONTH) == 31);
}
private static boolean isHolidays() {
if (!isEnabled()) return false;
if (!isEnabled()) {
return false;
}
Calendar today = new GregorianCalendar();
return today.get(Calendar.MONTH) == 11 && (today.get(Calendar.DAY_OF_MONTH) > 20 && today.get(Calendar.DAY_OF_MONTH) < 27);
return today.get(Calendar.MONTH) == Calendar.DECEMBER &&
(today.get(Calendar.DAY_OF_MONTH) > 20 && today.get(Calendar.DAY_OF_MONTH) < 27);
}
public static int getBrandColor() {
return isHalloween() ?
TvApp.getApplication().getResources().getColor(R.color.halloween_end) :
isHolidays() ?
TvApp.getApplication().getResources().getColor(R.color.holiday_end) :
Utils.getBrandColor();
private static void showHolidayMessage(final BaseActivity activity, final String title, final String message, final Integer icon, final Integer background) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// make sure they haven't moved away from that activity
if (TvApp.getApplication().getCurrentActivity() == null ||
TvApp.getApplication().getCurrentActivity() != activity) {
return;
}
activity.showMessage(title, message, 10000, icon, background);
}
}, 2000);
}
public static void showWelcomeMessage() {
final BaseActivity currentActivity = TvApp.getApplication().getCurrentActivity();
if (isHalloween() && currentActivity != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// make sure they haven't moved away from that activity
if (TvApp.getApplication().getCurrentActivity() == null || TvApp.getApplication().getCurrentActivity() != currentActivity) return;
MediaPlayer mp = MediaPlayer.create(TvApp.getApplication(), R.raw.howl);
mp.start();
currentActivity.showMessage("Happy Halloween!", "Try some of our spooky suggestions", 10000, R.drawable.ghost, R.drawable.orange_gradient);
}
}, 2000);
} else
if (isHolidays() && currentActivity != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// make sure they haven't moved away from that activity
if (TvApp.getApplication().getCurrentActivity() == null || TvApp.getApplication().getCurrentActivity() != currentActivity) return;
MediaPlayer mp = MediaPlayer.create(TvApp.getApplication(), R.raw.sleighbells);
mp.start();
currentActivity.showMessage("Happy Holidays!", "Try some of our holiday suggestions", 10000, R.drawable.snowflake, R.drawable.holiday_gradient);
}
}, 2000);
if (currentActivity != null) {
if (isHalloween()) {
showHolidayMessage(currentActivity,
currentActivity.getString(R.string.title_suggestions_halloween),
currentActivity.getString(R.string.desc_suggestions_halloween),
R.drawable.ic_ghost,
R.drawable.orange_gradient);
} else if (isHolidays()) {
showHolidayMessage(currentActivity,
currentActivity.getString(R.string.title_suggestions_holiday),
currentActivity.getString(R.string.desc_suggestions_holiday),
R.drawable.ic_snowflake,
R.drawable.holiday_gradient);
}
}
}
public static String[] getSpecialGenres() {
if (isHalloween()) {
return new String[] {"Halloween","Horror"};
return new String[]{"Halloween", "Horror"};
}
if (isHolidays()) {
return new String[] {"Holiday","Holidays","Christmas"};
return new String[]{"Holiday", "Holidays", "Christmas"};
}
return null;
}
public static String getSuggestionTitle() {
return isHalloween() ? "Spooky Suggestions" : isHolidays() ? "Holiday Suggestions" : "Suggestions";
int labelId = R.string.lbl_suggestions;
if (isHalloween()) {
labelId = R.string.lbl_suggestions_halloween;
} else if (isHolidays()) {
labelId = R.string.lbl_suggestions_holiday;
}
return TvApp.getApplication().getCurrentActivity().getString(labelId);
}
}

View File

@@ -12,5 +12,20 @@ public enum QueryType {
StaticChapters,
StaticServers,
Users,
Search, Specials, Trailers, LiveTvChannel, LiveTvProgram, LiveTvRecording, LiveTvRecordingGroup, StaticItems, Persons, StaticAudioQueueItems, AlbumArtists, AudioPlaylists, ContinueWatching, LatestItems, SeriesTimer, Premieres
Search,
Specials,
Trailers,
LiveTvChannel,
LiveTvProgram,
LiveTvRecording,
LiveTvRecordingGroup,
StaticItems,
Persons,
StaticAudioQueueItems,
AlbumArtists,
AudioPlaylists,
ContinueWatching,
LatestItems,
SeriesTimer,
Premieres
}

View File

@@ -0,0 +1,29 @@
package org.jellyfin.androidtv.search;
import android.os.Bundle;
import org.jellyfin.androidtv.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.itemhandling.ItemLauncher;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import androidx.leanback.app.SearchSupportFragment;
import androidx.leanback.widget.ListRow;
public class LeanbackSearchFragment extends SearchSupportFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create provider
SearchProvider searchProvider = new SearchProvider(getContext(), getActivity().getIntent().getBooleanExtra("MusicOnly", false));
setSearchResultProvider(searchProvider);
// Create click listener
setOnItemViewClickedListener((itemViewHolder, item, rowViewHolder, row) -> {
if (!(item instanceof BaseRowItem)) return;
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow) row).getAdapter(), ((BaseRowItem) item).getIndex(), getActivity());
});
}
}

View File

@@ -1,113 +0,0 @@
package org.jellyfin.androidtv.search;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.SearchFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import com.squareup.picasso.Target;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.imagehandling.PicassoBackgroundManagerTarget;
import org.jellyfin.androidtv.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.itemhandling.ItemLauncher;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.util.Utils;
import java.util.Timer;
/**
* Created by Eric on 1/26/2015.
*/
public class MySearchFragment extends SearchFragment
implements SearchFragment.SearchResultProvider {
private static final int SEARCH_DELAY_MS = 1500;
private final Handler mHandler = new Handler();
private ArrayObjectAdapter mRowsAdapter;
private SearchRunnable mDelayedLoad;
private Drawable mDefaultBackground;
private Target mBackgroundTarget;
private DisplayMetrics mMetrics;
private Timer mBackgroundTimer;
private String mBackgroundUrl;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
setSearchResultProvider(this);
setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
if (!(item instanceof BaseRowItem)) return;
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow)row).getAdapter(),((BaseRowItem) item).getIndex(), getActivity());
}
});
mDelayedLoad = new SearchRunnable(getActivity(), mRowsAdapter, getActivity().getIntent().getBooleanExtra("MusicOnly",false));
prepareBackgroundManager();
}
@Override
public ObjectAdapter getResultsAdapter() {
return mRowsAdapter;
}
private boolean textEdited = false;
@Override
public boolean onQueryTextChange(String newQuery) {
textEdited = true;
if (Utils.isNonEmpty(newQuery)) {
mHandler.removeCallbacks(mDelayedLoad);
mDelayedLoad.setQueryString(newQuery);
mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
} else {
mRowsAdapter.clear();
}
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
if (!textEdited) {
if (Utils.isNonEmpty(query)) {
mHandler.removeCallbacks(mDelayedLoad);
mDelayedLoad.setQueryString(query);
mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
} else {
mRowsAdapter.clear();
}
}
return true;
}
private void prepareBackgroundManager() {
BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
backgroundManager.attach(getActivity().getWindow());
mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
mDefaultBackground = null;
mMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
}
}

View File

@@ -1,108 +0,0 @@
package org.jellyfin.androidtv.search;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.itemhandling.ItemLauncher;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
/**
* Created by Eric on 4/8/2015.
*/
public class MySearchFragmentNoSpeech extends Fragment {
private static final int SEARCH_DELAY_MS = 1500;
private RowsFragment mRowsFragment;
private EditText mSearchField;
private ArrayObjectAdapter mRowsAdapter;
private SearchRunnable mDelayedLoad;
private Handler mHandler = new Handler();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_empty_frame, container, false);
// Inject the RowsFragment in the results container
if (getChildFragmentManager().findFragmentById(R.id.emptyFrame) == null) {
mRowsFragment = new RowsFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.emptyFrame, mRowsFragment).commit();
} else {
mRowsFragment = (RowsFragment) getChildFragmentManager()
.findFragmentById(R.id.emptyFrame);
}
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
mRowsFragment.setAdapter(mRowsAdapter);
// Setup item selection
mRowsFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
if (!(item instanceof BaseRowItem)) return;
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow)row).getAdapter(), ((BaseRowItem) item).getIndex(), getActivity());
}
});
mDelayedLoad = new SearchRunnable(getActivity(), mRowsAdapter, getActivity().getIntent().getBooleanExtra("MusicOnly",false));
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Setup search field listening
mSearchField = (EditText) getActivity().findViewById(R.id.searchField);
mSearchField.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() > 0) {
mHandler.removeCallbacks(mDelayedLoad);
mDelayedLoad.setQueryString(s.toString());
mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
} else {
mRowsAdapter.clear();
}
}
});
}
}

View File

@@ -3,26 +3,34 @@ package org.jellyfin.androidtv.search;
import android.os.Bundle;
import android.speech.SpeechRecognizer;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.base.BaseActivity;
public class SearchActivity extends BaseActivity {
import androidx.fragment.app.Fragment;
public class SearchActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (SpeechRecognizer.isRecognitionAvailable(TvApp.getApplication())) {
setContentView(R.layout.activity_search);
} else {
setContentView(R.layout.activity_search_no_speech);
}
boolean isSpeechEnabled = SpeechRecognizer.isRecognitionAvailable(this);
// Determine fragment to use
Fragment searchFragment = isSpeechEnabled
? new LeanbackSearchFragment()
: new TextSearchFragment();
// Add fragment
getSupportFragmentManager()
.beginTransaction()
.replace(android.R.id.content, searchFragment)
.commit();
}
@Override
public boolean onSearchRequested() {
//re-start us
this.recreate();
// Reset layout
recreate();
return true;
}
}

View File

@@ -0,0 +1,67 @@
package org.jellyfin.androidtv.search;
import android.content.Context;
import android.os.Handler;
import org.jellyfin.androidtv.util.Utils;
import androidx.leanback.app.SearchSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.ListRowPresenter;
import androidx.leanback.widget.ObjectAdapter;
public class SearchProvider implements SearchSupportFragment.SearchResultProvider {
private static final int SEARCH_DELAY_MS = 1500;
private final Handler mHandler = new Handler();
private ArrayObjectAdapter mRowsAdapter;
private SearchRunnable mDelayedLoad;
SearchProvider(Context context, boolean musicOnly) {
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
mDelayedLoad = new SearchRunnable(context, mRowsAdapter, musicOnly);
}
@Override
public ObjectAdapter getResultsAdapter() {
return mRowsAdapter;
}
@Override
public boolean onQueryTextChange(String query) {
search(query, true);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
search(query, false);
return true;
}
/**
* Update search results
*
* @param query String to search for
* @param delayed When true the search is delayed by [SEARCH_DELAY_MS] milliseconds
*/
private void search(String query, boolean delayed) {
// Clear results when query is empty
if (Utils.isEmpty(query)) {
mRowsAdapter.clear();
return;
}
// Remove current delayed search (if any)
mHandler.removeCallbacks(mDelayedLoad);
// Update search string
mDelayedLoad.setQueryString(query);
// Schedule search depending on [delayed]
if (delayed) mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
else mHandler.post(mDelayedLoad);
}
}

View File

@@ -1,98 +1,128 @@
package org.jellyfin.androidtv.search;
import android.app.Activity;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.content.Context;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.presentation.CardPresenter;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.model.search.SearchQuery;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import java.util.ArrayList;
/**
* Created by Eric on 4/8/2015.
*/
public class SearchRunnable implements Runnable {
private String searchString;
private Activity activity;
private Context context;
private ArrayObjectAdapter mRowsAdapter;
private boolean musicOnly;
private int searchesReceived;
private ArrayList<ItemRowAdapter> searchItemRows;
public void setQueryString(String value) {
searchString = value;
}
public SearchRunnable(Activity activity, ArrayObjectAdapter adapter, boolean musicOnly) {
this.activity = activity;
public SearchRunnable(Context context, ArrayObjectAdapter adapter, boolean musicOnly) {
this.context = context;
this.mRowsAdapter = adapter;
this.musicOnly = musicOnly;
this.searchItemRows = new ArrayList<>();
}
@Override
public void run() {
mRowsAdapter.clear();
searchItemRows.clear();
searchesReceived = 0;
if (!musicOnly) {
//Get search results by type
SearchQuery people = getSearchQuery(new String[] {"Person","People"});
ItemRowAdapter peopleAdapter = new ItemRowAdapter(people, new CardPresenter(), mRowsAdapter);
ListRow peopleRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_people)), peopleAdapter);
peopleAdapter.setRow(peopleRow);
peopleAdapter.Retrieve();
SearchQuery movies = getSearchQuery(new String[] {"Movie", "BoxSet"});
ItemRowAdapter movieAdapter = new ItemRowAdapter(movies, new CardPresenter(), mRowsAdapter);
ListRow movieRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_movies)), movieAdapter);
ListRow movieRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_movies)), movieAdapter);
movieAdapter.setRow(movieRow);
movieAdapter.Retrieve();
retrieveSearchResult(movieAdapter);
SearchQuery tv = getSearchQuery(new String[] {"Series","Episode"});
SearchQuery tvSeries = getSearchQuery(new String[] {"Series"});
ItemRowAdapter tvSeriesAdapter = new ItemRowAdapter(tvSeries, new CardPresenter(), mRowsAdapter);
ListRow tvSeriesRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_series)), tvSeriesAdapter);
tvSeriesAdapter.setRow(tvSeriesRow);
retrieveSearchResult(tvSeriesAdapter);
SearchQuery tv = getSearchQuery(new String[] {"Episode"});
ItemRowAdapter tvAdapter = new ItemRowAdapter(tv, new CardPresenter(), mRowsAdapter);
ListRow tvRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_tv)), tvAdapter);
ListRow tvRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_episodes)), tvAdapter);
tvAdapter.setRow(tvRow);
tvAdapter.Retrieve();
retrieveSearchResult(tvAdapter);
SearchQuery people = getSearchQuery(new String[] {"Person","People"});
ItemRowAdapter peopleAdapter = new ItemRowAdapter(people, new CardPresenter(), mRowsAdapter);
ListRow peopleRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_people)), peopleAdapter);
peopleAdapter.setRow(peopleRow);
retrieveSearchResult(peopleAdapter);
SearchQuery videos = getSearchQuery(new String[] {"Video"});
ItemRowAdapter videoAdapter = new ItemRowAdapter(videos, new CardPresenter(), mRowsAdapter);
ListRow videoRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_videos)), videoAdapter);
ListRow videoRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_videos)), videoAdapter);
videoAdapter.setRow(videoRow);
videoAdapter.Retrieve();
retrieveSearchResult(videoAdapter);
SearchQuery recordings = getSearchQuery(new String[] {"Recording"});
ItemRowAdapter recordingAdapter = new ItemRowAdapter(recordings, new CardPresenter(), mRowsAdapter);
ListRow recordingRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_recordings)), recordingAdapter);
ListRow recordingRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_recordings)), recordingAdapter);
recordingAdapter.setRow(recordingRow);
recordingAdapter.Retrieve();
retrieveSearchResult(recordingAdapter);
SearchQuery programs = getSearchQuery(new String[] {"Program"});
ItemRowAdapter programAdapter = new ItemRowAdapter(programs, new CardPresenter(), mRowsAdapter);
ListRow programRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_programs)), programAdapter);
ListRow programRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_programs)), programAdapter);
programAdapter.setRow(programRow);
programAdapter.Retrieve();
retrieveSearchResult(programAdapter);
}
SearchQuery artists = getSearchQuery(new String[] {"MusicArtist"});
ItemRowAdapter artistAdapter = new ItemRowAdapter(artists, new CardPresenter(), mRowsAdapter);
ListRow artistRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_artists)), artistAdapter);
ListRow artistRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_artists)), artistAdapter);
artistAdapter.setRow(artistRow);
artistAdapter.Retrieve();
retrieveSearchResult(artistAdapter);
SearchQuery albums = getSearchQuery(new String[] {"MusicAlbum"});
ItemRowAdapter albumAdapter = new ItemRowAdapter(albums, new CardPresenter(), mRowsAdapter);
ListRow albumRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_albums)), albumAdapter);
ListRow albumRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_albums)), albumAdapter);
albumAdapter.setRow(albumRow);
albumAdapter.Retrieve();
retrieveSearchResult(albumAdapter);
SearchQuery songs = getSearchQuery(new String[] {"Audio"});
ItemRowAdapter songAdapter = new ItemRowAdapter(songs, new CardPresenter(), mRowsAdapter);
ListRow songRow = new ListRow(new HeaderItem(activity.getString(R.string.lbl_songs)), songAdapter);
ListRow songRow = new ListRow(new HeaderItem(context.getString(R.string.lbl_songs)), songAdapter);
songAdapter.setRow(songRow);
songAdapter.Retrieve();
retrieveSearchResult(songAdapter);
}
private void retrieveSearchResult(ItemRowAdapter itemRow) {
searchItemRows.add(itemRow);
itemRow.setRetrieveFinishedListener(new EmptyResponse() {
@Override
public void onResponse() {
searchesReceived++;
if (searchesReceived == searchItemRows.size()){
for (ItemRowAdapter itemRowAdapter : searchItemRows) {
itemRowAdapter.addToParentIfResultsReceived();
}
}
}
});
itemRow.Retrieve();
}
private SearchQuery getSearchQuery(String[] itemTypes) {
SearchQuery query = new SearchQuery();
query.setLimit(50);

View File

@@ -0,0 +1,84 @@
package org.jellyfin.androidtv.search;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.itemhandling.ItemLauncher;
import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import androidx.fragment.app.Fragment;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ListRow;
public class TextSearchFragment extends Fragment implements TextWatcher, TextView.OnEditorActionListener {
private SearchProvider searchProvider;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
searchProvider = new SearchProvider(getContext(), getActivity().getIntent().getBooleanExtra("MusicOnly", false));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_search_text, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add event listeners
EditText searchBar = getActivity().findViewById(R.id.search_bar);
searchBar.addTextChangedListener(this);
searchBar.setOnEditorActionListener(this);
// Set up result fragment
RowsSupportFragment rowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(R.id.results_frame);
rowsSupportFragment.setAdapter(searchProvider.getResultsAdapter());
// Create click listener
rowsSupportFragment.setOnItemViewClickedListener((itemViewHolder, item, rowViewHolder, row) -> {
if (!(item instanceof BaseRowItem)) return;
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow) row).getAdapter(), ((BaseRowItem) item).getIndex(), getActivity());
});
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// No implementation
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// No implementation
}
@Override
public void afterTextChanged(Editable s) {
searchProvider.onQueryTextChange(s.toString());
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// Detect keyboard "submit" actions
if (actionId == EditorInfo.IME_ACTION_SEARCH)
searchProvider.onQueryTextSubmit(v.getText().toString());
// Return "false" to automatically close keyboard
return false;
}
}

View File

@@ -1,8 +1,6 @@
package org.jellyfin.androidtv.settings;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
@@ -15,17 +13,13 @@ import android.widget.TextView;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.startup.LogonCredentials;
import org.jellyfin.androidtv.model.LogonCredentials;
import org.jellyfin.androidtv.util.DeviceUtils;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
import java.io.IOException;
/**
* A simple {@link Fragment} subclass.
*/
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
@@ -34,7 +28,6 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
@Override
@@ -42,7 +35,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
super.onActivityCreated(savedInstanceState);
// Set version info
TextView ver = (TextView) getActivity().findViewById(R.id.settings_version_info);
TextView ver = getActivity().findViewById(R.id.settings_version_info);
ver.setText(Utils.getVersionString());
// conditionally hide options that don't apply
@@ -77,8 +70,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
@Override
@@ -100,8 +92,8 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
if (key.equals("pref_send_path_external") && ((CheckBoxPreference)findPreference(key)).isChecked()) {
new AlertDialog.Builder(getActivity())
.setTitle("W A R N I N G")
.setMessage("This feature will only work if you have properly setup your library on the server with network paths or setup Path Substitution AND the external player app you are using can directly access these locations over the network. If playback fails or you didn't understand any of that, disable this option.")
.setTitle(getString(R.string.lbl_warning))
.setMessage(getString(R.string.msg_external_path))
.setPositiveButton(R.string.btn_got_it, null)
.show();
}
@@ -121,7 +113,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
if (!TvApp.getApplication().getConfiguredAutoCredentials().getUserDto().getId().equals(TvApp.getApplication().getCurrentUser().getId())) {
listPreference.setEnabled(false);
pwPrompt.setEnabled(false);
extra = getActivity().getString(R.string.lbl_paren_login_as)+TvApp.getApplication().getConfiguredAutoCredentials().getUserDto().getName()+getActivity().getString(R.string.lbl_to_change_paren);
extra = getActivity().getString(R.string.lbl_paren_login_as) + TvApp.getApplication().getConfiguredAutoCredentials().getUserDto().getName() + getActivity().getString(R.string.lbl_to_change_paren);
}
listPreference.setSummary(getActivity().getString(R.string.lbl_login_as) + TvApp.getApplication().getConfiguredAutoCredentials().getUserDto().getName() + extra);
} else {
@@ -129,6 +121,18 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
pwPrompt.setEnabled(false);
}
} else {
if (listPreference.getKey().equals("pref_video_player")) {
boolean isExternal = listPreference.getValue().equals("external");
// enable/disable other related items
Preference direct = findPreference("pref_send_path_external");
if (direct != null) direct.setEnabled(isExternal);
for (String key : extPlayerVideoDep) {
Preference pref = findPreference(key);
if (pref != null) pref.setEnabled(!isExternal);
}
}
listPreference.setSummary(listPreference.getEntry());
}
}
@@ -139,22 +143,6 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
// enable other live tv direct only options
Preference live = findPreference("pref_enable_vlc_livetv");
if (live != null) live.setEnabled(cb.isChecked());
// Preference shift = findPreference("pref_live_shift");
// if (shift != null) shift.setEnabled(!cb.isChecked());
} else
// if (cb.getKey().equals("pref_live_shift")) {
// // enable/disable related options
// Preference direct = findPreference("pref_live_direct");
// if (direct != null) direct.setEnabled(!cb.isChecked());
// } else
if (cb.getKey().equals("pref_video_use_external")) {
// enable/disable other related items
Preference direct = findPreference("pref_send_path_external");
if (direct != null) direct.setEnabled(cb.isChecked());
for (String key: extPlayerVideoDep) {
Preference pref = findPreference(key);
if (pref != null) pref.setEnabled(!cb.isChecked());
}
} else if (cb.getKey().equals("pref_live_tv_use_external")) {
// enable/disable other related items
for (String key: extPlayerLiveTvDep) {
@@ -163,7 +151,5 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
}
}
}
}
}

View File

@@ -14,11 +14,12 @@ import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
import org.jellyfin.apiclient.model.dto.UserDto;
import androidx.fragment.app.FragmentActivity;
public class DpadPwActivity extends Activity {
public class DpadPwActivity extends FragmentActivity {
private long lastKeyDown = Long.MAX_VALUE;
private long lastKeyUp = 0;

View File

@@ -1,13 +1,15 @@
package org.jellyfin.androidtv.startup;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.view.KeyEvent;
import android.widget.Toast;
@@ -28,30 +30,25 @@ import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
import java.util.ArrayList;
import java.util.List;
import org.jellyfin.apiclient.interaction.GsonJsonSerializer;
import org.jellyfin.apiclient.model.apiclient.ServerInfo;
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
/**
* Created by Eric on 12/4/2014.
*/
public class SelectServerFragment extends CustomBrowseFragment {
private static final int ENTER_MANUALLY = 0;
private List<ServerInfo> mServers = new ArrayList<>();
@Override
public void onActivityCreated(Bundle savedInstanceState) {
GsonJsonSerializer serializer = TvApp.getApplication().getSerializer();
String[] passedItems = getActivity().getIntent().getStringArrayExtra("Servers");
if (passedItems != null) {
for (String json : passedItems) {
mServers.add((ServerInfo) serializer.DeserializeFromString(json, ServerInfo.class));
mServers.add(serializer.DeserializeFromString(json, ServerInfo.class));
}
}
mActivity = (BaseActivity) getActivity();
super.onActivityCreated(savedInstanceState);
}
@Override
@@ -59,7 +56,7 @@ public class SelectServerFragment extends CustomBrowseFragment {
super.addAdditionalRows(rowAdapter);
HeaderItem serverHeader = new HeaderItem(rowAdapter.size(), mApplication.getString(R.string.lbl_select_server));
ItemRowAdapter serverAdapter = new ItemRowAdapter(mServers.toArray(new ServerInfo[mServers.size()]), new CardPresenter(), rowAdapter);
ItemRowAdapter serverAdapter = new ItemRowAdapter(mServers.toArray(new ServerInfo[0]), new CardPresenter(), rowAdapter);
serverAdapter.Retrieve();
rowAdapter.add(new ListRow(serverHeader, serverAdapter));
@@ -67,7 +64,7 @@ public class SelectServerFragment extends CustomBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(ENTER_MANUALLY, mApplication.getString(R.string.lbl_enter_manually), R.drawable.edit));
gridRowAdapter.add(new GridButton(ENTER_MANUALLY, mApplication.getString(R.string.lbl_enter_manually), R.drawable.tile_edit));
rowAdapter.add(new ListRow(gridHeader, gridRowAdapter));
}
@@ -87,9 +84,8 @@ public class SelectServerFragment extends CustomBrowseFragment {
@Override
public void onMessageReceived(CustomMessage message) {
switch (message) {
case RemoveCurrentItem:
((ItemRowAdapter)mCurrentRow.getAdapter()).remove(mCurrentItem);
((ItemRowAdapter) mCurrentRow.getAdapter()).remove(mCurrentItem);
break;
}
}
@@ -101,7 +97,6 @@ public class SelectServerFragment extends CustomBrowseFragment {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof GridButton) {
switch (((GridButton) item).getId()) {
case ENTER_MANUALLY:
@@ -115,5 +110,4 @@ public class SelectServerFragment extends CustomBrowseFragment {
}
}
}
}

View File

@@ -1,12 +1,13 @@
package org.jellyfin.androidtv.startup;
import android.app.Activity;
import android.os.Bundle;
import org.jellyfin.androidtv.R;
import androidx.fragment.app.FragmentActivity;
public class SelectUserActivity extends Activity {
public class SelectUserActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@@ -2,13 +2,13 @@ package org.jellyfin.androidtv.startup;
import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.widget.Toast;
import org.jellyfin.androidtv.R;
@@ -18,15 +18,14 @@ import org.jellyfin.androidtv.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.presentation.CardPresenter;
import org.jellyfin.androidtv.presentation.GridButtonPresenter;
import org.jellyfin.androidtv.ui.GridButton;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
import java.util.ArrayList;
import java.util.List;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.interaction.GsonJsonSerializer;
import org.jellyfin.apiclient.model.apiclient.ServerInfo;
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
public class SelectUserFragment extends CustomBrowseFragment {
private static final int ENTER_MANUALLY = 0;
@@ -56,8 +55,8 @@ public class SelectUserFragment extends CustomBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(ENTER_MANUALLY, mApplication.getString(R.string.lbl_enter_manually), R.drawable.edit));
gridRowAdapter.add(new GridButton(SWITCH_SERVER, mApplication.getString(R.string.lbl_switch_server), R.drawable.server));
gridRowAdapter.add(new GridButton(ENTER_MANUALLY, mApplication.getString(R.string.lbl_enter_manually), R.drawable.tile_edit));
gridRowAdapter.add(new GridButton(SWITCH_SERVER, mApplication.getString(R.string.lbl_switch_server), R.drawable.tile_port_server));
rowAdapter.add(new ListRow(gridHeader, gridRowAdapter));
}

View File

@@ -8,9 +8,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import org.jellyfin.androidtv.BuildConfig;
import org.jellyfin.androidtv.R;
@@ -24,26 +21,28 @@ import org.jellyfin.androidtv.playback.PlaybackManager;
import org.jellyfin.androidtv.util.ProfileHelper;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.AuthenticationHelper;
import java.util.ArrayList;
import org.jellyfin.apiclient.interaction.AndroidConnectionManager;
import org.jellyfin.apiclient.interaction.AndroidDevice;
import org.jellyfin.apiclient.interaction.ApiEventListener;
import org.jellyfin.apiclient.interaction.ConnectionResult;
import org.jellyfin.apiclient.interaction.IConnectionManager;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.interaction.AndroidConnectionManager;
import org.jellyfin.apiclient.interaction.AndroidDevice;
import org.jellyfin.apiclient.interaction.GsonJsonSerializer;
import org.jellyfin.apiclient.interaction.VolleyHttpClient;
import org.jellyfin.apiclient.model.apiclient.ConnectionState;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.logging.ILogger;
import org.jellyfin.apiclient.model.serialization.IJsonSerializer;
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
import org.jellyfin.apiclient.model.session.ClientCapabilities;
import org.jellyfin.apiclient.model.session.GeneralCommandType;
import java.util.ArrayList;
public class StartupActivity extends Activity {
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
public class StartupActivity extends FragmentActivity {
private static final int NETWORK_PERMISSION = 1;
private TvApp application;
@@ -60,7 +59,7 @@ public class StartupActivity extends Activity {
//Migrate prefs
if (Integer.parseInt(application.getConfigVersion()) < 2) {
application.getSystemPrefs().edit().putString("sys_pref_config_version", "2").commit();
application.getSystemPrefs().edit().putString("sys_pref_config_version", "2").apply();
}
if (Integer.parseInt(application.getConfigVersion()) < 3) {
application.getPrefs().edit().putString("pref_max_bitrate", "0").apply();
@@ -71,12 +70,12 @@ public class StartupActivity extends Activity {
application.getPrefs().edit().putBoolean("pref_enable_info_panel", false).apply();
application.getSystemPrefs().edit().putString("sys_pref_config_version", "4").apply();
}
// if (Integer.parseInt(application.getConfigVersion()) < 5) {
// application.getPrefs().edit().putBoolean("pref_live_shift", true).apply();
// application.getPrefs().edit().putBoolean("pref_live_direct", false).apply();
// application.getSystemPrefs().edit().putString("sys_pref_config_version", "5").apply();
// }
if (Integer.parseInt(application.getConfigVersion()) < 5) {
boolean useExternal = application.getPrefs().getBoolean("pref_video_use_external", false);
application.getPrefs().edit().putString("pref_video_player", useExternal ? "external" : "auto").apply();
application.getSystemPrefs().edit().putString("sys_pref_config_version", "5").apply();
}
//Ensure we have prefs
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
@@ -97,14 +96,12 @@ public class StartupActivity extends Activity {
// go straight into last connection
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
} else {
//clear audio queue in case left over from last run
MediaManager.clearAudioQueue();
MediaManager.clearVideoQueue();
establishConnection(this);
}
}
@Override
@@ -153,12 +150,8 @@ public class StartupActivity extends Activity {
capabilities.setDeviceProfile(new AndroidProfile(ProfileHelper.getProfileOptions()));
capabilities.setSupportsMediaControl(true);
capabilities.setSupportedCommands(supportedCommands);
// TODO: Add new icon url
// capabilities.setIconUrl("");
IJsonSerializer jsonSerializer = new GsonJsonSerializer();
GsonJsonSerializer jsonSerializer = new GsonJsonSerializer();
ApiEventListener apiEventListener = new TvApiEventListener();
final IConnectionManager connectionManager = new AndroidConnectionManager(application,
@@ -172,8 +165,7 @@ public class StartupActivity extends Activity {
apiEventListener);
application.setConnectionManager(connectionManager);
application.setSerializer((GsonJsonSerializer) jsonSerializer);
application.setSerializer(jsonSerializer);
application.setPlaybackManager(new PlaybackManager(new AndroidDevice(application), logger));
//See if we are coming in via direct entry
@@ -188,11 +180,21 @@ public class StartupActivity extends Activity {
connectionManager.Connect(application.getConfiguredAutoCredentials().getServerInfo(), new Response<ConnectionResult>() {
@Override
public void onResponse(ConnectionResult response) {
// Saved server login is unavailable
if (response.getState() == ConnectionState.Unavailable) {
Utils.showToast( activity, "Unable to connect to configured server "+application.getConfiguredAutoCredentials().getServerInfo().getName());
connectAutomatically(connectionManager, activity);
Utils.showToast(activity, R.string.msg_error_server_unavailable + ": " + application.getConfiguredAutoCredentials().getServerInfo().getName());
AuthenticationHelper.automaticSignIn(connectionManager, activity);
return;
}
// Check the server version
if (!response.getServers().isEmpty() &&
!AuthenticationHelper.isSupportedServerVersion(response.getServers().get(0))) {
Utils.showToast(activity, activity.getString(R.string.msg_error_server_version, TvApp.MINIMUM_SERVER_VERSION));
AuthenticationHelper.automaticSignIn(connectionManager, activity);
return;
}
// Connected to server - load user and prompt for pw if necessary
application.setLoginApiClient(response.getApiClient());
response.getApiClient().GetUserAsync(application.getConfiguredAutoCredentials().getUserDto().getId(), new Response<UserDto>() {
@@ -208,16 +210,13 @@ public class StartupActivity extends Activity {
Utils.processPasswordEntry(activity, response, application.getDirectItemId());
} else {
//Can just go right into details
Intent detailsIntent = new Intent(activity, FullDetailsActivity.class);
detailsIntent.putExtra("ItemId", application.getDirectItemId());
startActivity(detailsIntent);
}
} else {
if (response.getHasPassword() && application.getPrefs().getBoolean("pref_auto_pw_prompt", false)) {
Utils.processPasswordEntry(activity, response);
} else {
Intent intent = new Intent(activity, MainActivity.class);
activity.startActivity(intent);
@@ -228,11 +227,11 @@ public class StartupActivity extends Activity {
@Override
public void onError(Exception exception) {
application.getLogger().ErrorException("Error Signing in", exception);
Utils.reportError(activity, "Error Signing In");
Utils.showToast(activity, R.string.msg_error_signin);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
connectAutomatically(connectionManager, activity);
AuthenticationHelper.automaticSignIn(connectionManager, activity);
}
}, 5000);
}
@@ -241,29 +240,12 @@ public class StartupActivity extends Activity {
@Override
public void onError(Exception exception) {
Utils.showToast( activity, "Unable to connect to configured server "+application.getConfiguredAutoCredentials().getServerInfo().getName());
connectAutomatically(connectionManager, activity);
Utils.showToast( activity, R.string.msg_error_connecting_server + ": " + application.getConfiguredAutoCredentials().getServerInfo().getName());
AuthenticationHelper.automaticSignIn(connectionManager, activity);
}
});
} else {
connectAutomatically(connectionManager, activity);
AuthenticationHelper.automaticSignIn(connectionManager, activity);
}
}
private void connectAutomatically(final IConnectionManager connectionManager, final Activity activity){
connectionManager.Connect(new Response<ConnectionResult>() {
@Override
public void onResponse(final ConnectionResult response) {
AuthenticationHelper.handleConnectionResponse(connectionManager, activity, response);
}
@Override
public void onError(Exception exception) {
Utils.reportError(activity, "Error connecting");
}
});
}
}

View File

@@ -2,7 +2,8 @@ package org.jellyfin.androidtv.ui;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,7 +33,7 @@ public class AudioDelayPopup {
mPopup = new PopupWindow(layout, WIDTH, HEIGHT);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new BitmapDrawable()); // necessary for popup to dismiss
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mAnchor = anchor;

View File

@@ -44,12 +44,12 @@ public class ClockUserView extends RelativeLayout {
if (TvApp.getApplication().getCurrentUser().getPrimaryImageTag() != null) {
Picasso.with(context)
.load(ImageUtils.getPrimaryImageUrl(TvApp.getApplication().getCurrentUser(), TvApp.getApplication().getApiClient()))
.error(R.drawable.user)
.error(R.drawable.ic_user)
.resize(30,30)
.centerInside()
.into(userImage);
} else {
userImage.setImageResource(R.drawable.user);
userImage.setImageResource(R.drawable.ic_user);
}
}

View File

@@ -2,7 +2,8 @@ package org.jellyfin.androidtv.ui;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -45,7 +46,7 @@ public class DisplayPrefsPopup {
mPopup = new PopupWindow(layout, WIDTH, HEIGHT);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new BitmapDrawable()); // necessary for popup to dismiss
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mPopup.setAnimationStyle(R.style.PopupSlideInRight);
mAnchor = anchor;

View File

@@ -4,8 +4,8 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.widget.AppCompatTextView;
import androidx.core.app.ActivityOptionsCompat;
import androidx.appcompat.widget.AppCompatTextView;
import android.text.Layout;
import android.util.AttributeSet;
import android.view.SoundEffectConstants;

View File

@@ -2,13 +2,15 @@ package org.jellyfin.androidtv.ui;
import android.content.Context;
import android.graphics.Typeface;
import android.support.v7.widget.AppCompatTextView;
import androidx.appcompat.widget.AppCompatTextView;
import org.jellyfin.apiclient.model.dto.BaseItemType;
/**
* Created by Eric on 2/21/2015.
*/
public class GenreButton extends AppCompatTextView {
public GenreButton(Context context, Typeface font, int size, String text, String itemType) {
public GenreButton(Context context, Typeface font, int size, String text, BaseItemType itemType) {
super(context);
setTypeface(font);
setTextSize(size);

View File

@@ -22,7 +22,9 @@ public class GridButton {
return id;
}
public int getImageIndex() {return imageIndex;}
public int getImageIndex() {
return imageIndex;
}
@Override
public String toString() {

View File

@@ -1,13 +1,13 @@
package org.jellyfin.androidtv.ui;
import android.app.Fragment;
import androidx.fragment.app.Fragment;
import android.os.Bundle;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import androidx.leanback.widget.ObjectAdapter;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;

View File

@@ -1,7 +1,7 @@
package org.jellyfin.androidtv.ui;
import android.content.Context;
import android.support.v7.widget.AppCompatImageView;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
@@ -47,23 +47,21 @@ public class ImageButton extends AppCompatImageView {
mHelpView = helpView;
mHelpText = helpText;
setOnFocusChangeListener(focusChangeListener);
}
public void setHelpView(TextView view) {
mHelpView = view;
}
public void setHelpText(String text) { mHelpText = text; }
public void setHelpText(String text) {
mHelpText = text;
}
public void setState(int state) {
mState = state;
if (mSecondaryImage > 0) setImageResource(mState == STATE_SECONDARY ? mSecondaryImage : mPrimaryImage);
}
public void toggleState() {
setState(mState == STATE_PRIMARY ? STATE_SECONDARY : STATE_PRIMARY);
}
private OnFocusChangeListener focusChangeListener = new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -78,7 +76,9 @@ public class ImageButton extends AppCompatImageView {
}
};
public void setGotFocusListener(GotFocusEvent event) { mGotFocusListener = event; }
public void setGotFocusListener(GotFocusEvent event) {
mGotFocusListener = event;
}
public void setPrimaryImage(int mPrimaryImage) {
this.mPrimaryImage = mPrimaryImage;

View File

@@ -44,8 +44,7 @@ public class ItemListView extends FrameLayout {
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.item_list, this);
mContext = context;
mList = (LinearLayout) findViewById(R.id.songList);
mList = findViewById(R.id.songList);
}
public void setRowSelectedListener(ItemRowView.RowSelectedListener listener) { mRowSelectedListener = listener; }
@@ -61,9 +60,6 @@ public class ItemListView extends FrameLayout {
for (BaseItemDto item : items) {
addItem(item, i++);
}
//Throw in another item just to provide some padding at the end of the scroll
mList.addView(new TextView(mContext));
}
public void addItem(BaseItemDto item, int ndx) {
@@ -97,7 +93,8 @@ public class ItemListView extends FrameLayout {
if (response.getItems() != null) {
int i = 0;
for (BaseItemDto item : response.getItems()) {
View view = mList.getChildAt(i+1); // we have title view as first one
// we have title view as first one
View view = mList.getChildAt(i + 1);
if (view instanceof ItemRowView) {
ItemRowView row = (ItemRowView) view;
row.setItem(item, i++);

View File

@@ -13,6 +13,7 @@ import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.TvApp;
import org.jellyfin.androidtv.itemhandling.BaseRowItem;
import org.jellyfin.androidtv.util.InfoLayoutHelper;
import org.jellyfin.apiclient.model.dto.BaseItemType;
/**
* Created by Eric on 7/27/2015.
@@ -49,7 +50,7 @@ public class ItemPanel extends RelativeLayout {
public void setItem(BaseRowItem item) {
if (item != null) {
title.setText(item.getFullName() + (item.getItemType() == BaseRowItem.ItemType.BaseItem && "Episode".equals(item.getBaseItem().getType()) ? " - " + item.getName() : ""));
title.setText(item.getFullName() + (item.getItemType() == BaseRowItem.ItemType.BaseItem && item.getBaseItemType() == BaseItemType.Episode ? " - " + item.getName() : ""));
if (TvApp.getApplication().getCurrentActivity() != null) InfoLayoutHelper.addInfoRow(TvApp.getApplication().getCurrentActivity(), item, infoRow, true, true);
summary.setText(item.getSummary());
}

View File

@@ -2,7 +2,6 @@ package org.jellyfin.androidtv.ui;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.TextUtils;
@@ -29,7 +28,6 @@ public class ItemRowView extends FrameLayout {
TextView mItemName;
TextView mExtraName;
TextView mRunTime;
TextView mDivider;
TextView mWatchedMark;
Drawable normalBackground;
@@ -40,7 +38,6 @@ public class ItemRowView extends FrameLayout {
RowSelectedListener rowSelectedListener;
RowClickedListener rowClickedListener;
ItemRowView us;
public ItemRowView(Context context) {
super(context);
@@ -58,12 +55,12 @@ public class ItemRowView extends FrameLayout {
this.rowSelectedListener = rowSelectedListener;
this.rowClickedListener = rowClickedListener;
setItem(song, ndx);
us = this;
final ItemRowView itemRowView = this;
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
playSoundEffect(SoundEffectConstants.CLICK);
if (rowClickedListener != null) rowClickedListener.onRowClicked(us);
if (rowClickedListener != null) rowClickedListener.onRowClicked(itemRowView);
}
});
}
@@ -72,16 +69,14 @@ public class ItemRowView extends FrameLayout {
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.item_row, this);
mContext = context;
mWholeRow = (RelativeLayout) findViewById(R.id.wholeRow);
mIndexNo = (TextView) findViewById(R.id.indexNo);
mItemName = (TextView) findViewById(R.id.songName);
mExtraName = (TextView) findViewById(R.id.artistName);
mRunTime = (TextView) findViewById(R.id.runTime);
mDivider = (TextView) findViewById(R.id.divider);
mWatchedMark = (TextView) findViewById(R.id.watchedMark);
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);
normalBackground = mWholeRow.getBackground();
setFocusable(true);
}
@Override
@@ -100,15 +95,14 @@ public class ItemRowView extends FrameLayout {
mBaseItem = item;
ourIndex = ndx + 1;
mIndexNo.setText(Integer.toString(ourIndex));
switch (item.getType()) {
case "Audio":
switch (item.getBaseItemType()) {
case Audio:
mItemName.setText(item.getName());
String artist = item.getArtists() != null && item.getArtists().size() > 0 ? item.getArtists().get(0) : !TextUtils.isEmpty(item.getAlbumArtist()) ? item.getAlbumArtist() : null;
if (!TextUtils.isEmpty(artist)) {
mExtraName.setText(artist);
} else {
mExtraName.setVisibility(GONE);
mDivider.setVisibility(GONE);
}
break;
default:
@@ -119,7 +113,6 @@ public class ItemRowView extends FrameLayout {
} else {
mItemName.setText(item.getName());
mExtraName.setVisibility(GONE);
mDivider.setVisibility(GONE);
}
updateWatched();
break;
@@ -151,9 +144,9 @@ public class ItemRowView extends FrameLayout {
public boolean setPlaying(boolean playing) {
if (playing) {
mIndexNo.setBackgroundResource(R.drawable.eq_animation);
// TODO use decent animation for equalizer icon
mIndexNo.setBackgroundResource(R.drawable.ic_play);
mIndexNo.setText("");
((AnimationDrawable)mIndexNo.getBackground()).start();
} else {
mIndexNo.setBackgroundResource(R.drawable.blank10x10);
mIndexNo.setText(Integer.toString(ourIndex));
@@ -176,5 +169,4 @@ public class ItemRowView extends FrameLayout {
public static class RowClickedListener {
public void onRowClicked(ItemRowView row) {};
}
}

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -62,7 +62,7 @@ public class LiveProgramDetailPopup {
mPopup = new PopupWindow(layout, width, NORMAL_HEIGHT);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new BitmapDrawable()); // necessary for popup to dismiss
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mPopup.setAnimationStyle(R.style.PopupSlideInTop);
mDTitle = layout.findViewById(R.id.title);
mDTitle.setTypeface(roboto);

View File

@@ -64,7 +64,6 @@ public class NowPlayingBug extends FrameLayout {
Intent np = new Intent(TvApp.getApplication(), AudioNowPlayingActivity.class);
TvApp.getApplication().getCurrentActivity().startActivity(np);
}
}
});
}
@@ -89,7 +88,7 @@ public class NowPlayingBug extends FrameLayout {
@Override
public void onProgress(long pos) {
if (isShown()) npStatus.setText(TimeUtils.formatMillis(pos) + "/" + currentDuration);
if (isShown()) setStatus(pos);
}
@Override
@@ -97,7 +96,7 @@ public class NowPlayingBug extends FrameLayout {
if (hasQueue) {
// may have just added one so update display
setInfo(MediaManager.getCurrentAudioItem());
npStatus.setText(TimeUtils.formatMillis(MediaManager.getCurrentAudioPosition()) + "/" + currentDuration);
setStatus(MediaManager.getCurrentAudioPosition());
setVisibility(VISIBLE);
} else {
setVisibility(GONE);
@@ -113,7 +112,7 @@ public class NowPlayingBug extends FrameLayout {
MediaManager.addAudioEventListener(listener);
if (manageVisibility()) {
setInfo(MediaManager.getCurrentAudioItem());
npStatus.setText(TimeUtils.formatMillis(MediaManager.getCurrentAudioPosition()) + "/" + currentDuration);
setStatus(MediaManager.getCurrentAudioPosition());
}
}
}
@@ -124,17 +123,19 @@ public class NowPlayingBug extends FrameLayout {
if (!isInEditMode()) {
// unhook our events
MediaManager.removeAudioEventListener(listener);
}
}
private void setInfo(BaseItemDto item) {
if (item == null) return;
Picasso.with(context).load(ImageUtils.getPrimaryImageUrl(item, TvApp.getApplication().getApiClient())).error(R.drawable.audioicon).resize(35,35).centerInside().into(npIcon);
Picasso.with(context).load(ImageUtils.getPrimaryImageUrl(item, TvApp.getApplication().getApiClient())).error(R.drawable.ic_album).resize(35,35).centerInside().into(npIcon);
currentDuration = TimeUtils.formatMillis(item.getRunTimeTicks() != null ? item.getRunTimeTicks() / 10000 : 0);
npDesc.setText(item.getAlbumArtist() != null ? item.getAlbumArtist() : item.getName());
}
private void setStatus(long pos) {
npStatus.setText(String.format(getResources().getString(R.string.lbl_status), TimeUtils.formatMillis(pos), currentDuration));
}
public boolean manageVisibility() {

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