## Description
This PR overhauls how audio and subtitle track persistence works for
series! Previously, when you chose an audio or subtitle track for an
episode, only the language was saved and used for subsequent episodes.
This meant aspects about why that choice were lost such as a forced or
hearing impaired track.
This PR changes the persistence to include more details about the chosen
track. Additionally, the persistence is saved at both the season and
series levels.
## How it works
Selecting the track uses the first success option:
1. Episode specific - User has explicitly chosen an audio/subtitle track
for this episode
2. Season specific - User has chosen the track for another episode in
this season
3. Series specific - User has chosen the track for another episode in
this series
4. User preferences - User has _not_ chosen a track for the episode,
season, nor series, so determine track based on the preferred audio
language, preferred subtitle language, and subtitle mode
Additionally, the season or series specific choices use a scoring
algorithm to find the best suited track. For example, if the chosen
tracks for an episode in the season had the forced flag, this will be
preferred for subsequent episodes. Same for SDH/hearing impaired or
title, etc. This means that the common case, where a season has
substantially identical tracks, will use the same ones across all
episodes (after choosing for one, otherwise falling back to the series
or user preferences).
Finally, this also means that users that prefer SDH/hearing impaired
subtitles can be prioritized now, though there is no global preference
for this yet.
The previous track language choices are migrated to the new algorithm.
They lack the additional context, but this makes for a seamless
transition.
## UI changes
The context menu for choosing audio or subtitle tracks now displays why
that track was chosen.
### Related issues
Fixes#1795
Maybe fixes#1783
Related to #1136 & #1798Closes#1470
### Testing
Previous unit tests for track selection all pass, plus there are a few
additional unit tests. I've done some testing on the emulator and it
seems to work well. But I think real world testing will be needed.
## AI or LLM usage
None
## Description
This change applies only to subfolders that do not have their own saved
display settings.
When a subfolder has no saved settings, it now inherits the view type
and sort order from the closest ancestor with saved settings. Filter
settings are not inherited.
If the user changes the settings within a subfolder, those settings are
saved for that subfolder and take precedence over the inherited
settings.
### Related issues
Fixes#1768
### Testing
Tested an App Store flavor debug build on the following setup:
- Device: Xiaomi TV A Pro 43
- Jellyfin server version: 12.1
- Library: A hierarchical recording library stored on a NAS
I changed the view type and sort order at the top level of the library
and confirmed that both settings were inherited by untouched subfolders.
## Screenshots
N/A - no UI changes
## AI or LLM usage
Claude was used to write the changes in `CollectionFolderView.kt`.
I performed the testing on a physical Google TV device and verified the
behavior described above.
The commit also includes a `Co-Authored-By: Claude` trailer.
This description was written in Japanese and translated with an LLM.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Description
Adding per-format HDR override preferences (HDR10, HDR10+, Dolby Vision
Profile 5/7/8) to control device compatibility information sent to the
Jellyfin server, so users can force specific formats on or off
independently instead of disabling Dolby Vision as a whole.
Related issues
This is a common problem across the Jellyfin ecosystem, but there isn't
a specific issue for Wholphin. My experience is the issue is FireTV
(model and generation) + TV dependent + media dependent. I tested videos
from
https://kodi.wiki/view/Samples with the same DOVIWithHDR10 flag that
worked while other movies I have with It is a very frustrating user
experience and the current workaround of setting the bitrate really low
to force a transcode is sub-optimal IMO as it lowers the video quality.
This allows the user to maintain full quality while disabling only the
specific format(s) causing problems.
https://github.com/jellyfin/jellyfin-androidtv/issues/4021https://github.com/jellyfin/jellyfin-androidtv/issues/4892https://github.com/nova-video-player/aos-AVP/issues/1503https://www.reddit.com/r/jellyfin/comments/12jqoxh/dolby_vision_video_not_working_on_fire_stick_4k/
Testing
<!-- Describe how this change was tested and on what device(s) -->
FireTV 4K (gen 1) + Vizio
FireTV 4K Max (gen 1) + LG
FireTV 4K Max (gen 2) + Vizio
FireTV (gen 1) + Olevia
AI or LLM usage
Claude code assisted and wrote much of the code in this PR. I've
reviewed it all and run the pre-commit linting and formatting.
---------
Co-authored-by: Damontecres <damontecres@gmail.com>
## Description
This PR reworks some of the internal handling of changing users and auto
sign in.
1. Most recently used server/user is migrated from `AppPreferences` to
`MostRecentServerProvider`
2. Checking if a user profile is protected occurs at a lower level in
`ServerRepository` so fewer services need to check for this
3. Now always defaults to the most recent server's user list in cases
where the most recent user can't be switched to such as if the profile
is protected, auto signin is disabled, or and error occurred
The default implementation for `MostRecentServerProvider` uses Android's
`SharedPreferences`. `SharedPreferences` was already was being used to
track the most recent server for crash reporting (the crash reporting
process cannot use the Hilt DI). This PR enhances this and adds a better
API for saving/retrieving.
This also removes the last bit of "user specific" data from
`AppPreferences`.
### Related issues
None specific, but there have been a few related to auto sign-in &
intents that were recently fixed. This PR is a more detailed approach to
those same problems.
### Testing
Manually via the emulator. There are also unit tests for most (all?) of
the different settings and state combinations.
## Screenshots
N/A
## AI or LLM usage
None
## Description
When switching users, the current user's home page would start a reload
during the switch. This PR fixes that by comparing the user switch.
The underlying cause is that Room's flow is blunt and emits whenever the
table updates. So [during the user
switch](9b02804075/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt (L117-L118))
the table is updated causing [current user to
emit](9b02804075/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt (L71))
and the current user flow is updated causing the new user to emit.
### Related issues
N/A
### Testing
Emulator, switching users and switching locales
Can observe that `UserSwitchListener` only trigger once per user switch
instead of twice
## Screenshots
N/A
## AI or LLM usage
None
## Description
On the series overview page, if you play the second or third episode,
then return to the page, focus will move the first episode.
This did not happen in `v1.0.7` and the code for that page was minimally
changed, so I think this was caused by updating the compose version,
maybe the ordering on focus resolution or something.
This small change seems to reliably fix the issue though.
### Related issues
Fixes#2043
### Testing
Emulator
## Screenshots
N/A
## AI or LLM usage
None
## Description
If a collection is using separated types and there were 2+ empty rows
between types, then the forward composition wouldn't trigger and the
next row would not appear. In practice this means if you have a
Collection that contained Movies and Videos, you might not be able to
scroll down from Movies to Videos.
The fix is to filter out the empty rows before composing.
### Related issues
Fixes#2046
### Testing
Emulator
## Screenshots
N/A
## AI or LLM usage
None
## Description
Collecting minor bug fixes in one PR:
- If error occurs fetching a series, show the error on the UI instead of
loading forever
- Use song's artists on Now Playing page
- Fixes settings wrap around scrolling when an update is available
- Fix backdrop not clearing for items without backdrop
- Fix not picking an ABI-specific APK when updating a regular release
(maybe related to #1966)
- Add "Go To" for album context menus
- Fix handling of next/previous media remote button presses during video
playback
### Related issues
Fixes#1990Fixes#1965
### Testing
Emulator mostly
## Screenshots
N/A
## AI or LLM usage
None
## Description
Fixes a potential crash on any page that uses `HomePageContent`, such as
home page, recommended tab, & customize home page.
I think this is actually a bug in compose because it shouldn't pass
`Color.Unspecified` into the native color code. But similarly, using
`Color.Unspecified` in `animateColorAsState` doesn't make sense either.
### Related issues
Fixes#1995
### Testing
I was able to reproduce this consistently using the Purple theme on
customize home page by focusing on an empty row. This change doesn't
cause the crash.
## Screenshots
N/A
## AI or LLM usage
None
## Description
Wholphin already ships a baseline profile. AGP merges the profiles from the AndroidX libraries into `assets/dexopt/baseline.prof` on every release build, so a release APK built from `main` already has one at around 15 KB. It just never gets installed on the device.
The reason is in `app/src/main/AndroidManifest.xml`, which removes `androidx.startup.InitializationProvider` completely:
That was added in #372 so WorkManager could be set up on demand through `Configuration.Provider` and `HiltWorkerFactory`. The problem is that `androidx.profileinstaller` registers `ProfileInstallerInitializer` inside the same provider, so removing the provider removes that as well.
### Related issues
#553
### Testing
API 34 AOSP emulator, `defaultRelease` signed with a debug key, clean install each time. With this change, logcat shows the profile being installed.
Not tested on a physical Fire TV device.
## Screenshots
None, there are no UI changes.
## AI or LLM usage
I used Claude for this one.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Damontecres <damontecres@gmail.com>
## Description
Adds a new setting to pick a maximum resolution (by height). Any
playback that has a larger resolution will be transcoded down to the
maximum resolution.
Playback equal or less than the maximum resolution will not be affected
and will direct play if otherwise able. This setting is also independent
of the maximum bitrate (ie if max resolution is 4K and max bitrate is
2mbps, playing 4K will transcode).
This setting is pretty much equivalent to the Web UI setting "Maximum
Allowed Video Transcoding Resolution" with "Limit maximum supported
video resolution" enabled.
Note: this setting does not affect which version is chosen by default
### Related issues
Closes#756Closes#1594
### Testing
Manual emulator testing
## Screenshots
N/A
## AI or LLM usage
None
## Description
Updates the home page header & card text when a Season type is shown,
such as in recently added for a TV Show library.
The title or logo for the series is shown with the season name as the
title. The "quick details" has the year of the season. The card shows
the unplayed count of episodes.
### Related issues
Closes#1958
### Testing
Emulator over a few days to see different seasons
## Screenshots
<img width="313" height="153" alt="image"
src="https://github.com/user-attachments/assets/c41e9edd-9f2a-4f62-b76d-e452e83dd8bf"
/>
## AI or LLM usage
None
## Description
Add better error handling for server web socket messages that are
(technically) invalid.
For example, I talked to Niels and some third party plugins don't always
have the required fields. Since the server isn't as strict as the client
SDK, it's fine on the server but will generate an invalid message sent
to the client.
Also listens for server-side user configuration changes via web socket.
### Related issues
Fixes#1979
### Testing
Valid server messages are handled as normal and I did some manual
testing to push invalid JSON into the webSocketApi, but the code is
gnarly and not viable as an actual unit test
## Screenshots
N/A
## AI or LLM usage
None
## Description
Adds a "Prefer Dolby Vision over HDR10+" switch (off by default) under
Experimental settings. That screen only shows up once "Experimental
settings" is enabled under Advanced, and until then the switch is read
as off: it goes through `experimentalPreferences.get {}`, so nothing is
wrapped and the playback path is untouched.
Most current 4K web releases carry both Dolby Vision (profile 8.1) RPUs
and HDR10+ dynamic metadata in the same HEVC stream. Some MediaTek based
devices (Philips and TCL TVs, some Fire TV models) latch onto the HDR10+
metadata on those files and never engage Dolby Vision, even though a
Dolby Vision decoder is selected.
I isolated this on my Philips 55OLED810 by playing the same episode
three ways, direct play in all three cases:
| | Stream | TV shows |
|---|---|---|
| A | MKV with DV 8.1 + HDR10+ | HDR10+ |
| B | Same MKV with the HDR10+ metadata removed using
[hdr10plus_tool](https://github.com/quietvoid/hdr10plus_tool), DV
untouched | Dolby Vision |
| C | Same stream remuxed to MP4, both kinds of metadata present |
HDR10+ |
The container doesn't matter and the Dolby Vision signaling is picked up
correctly. The only thing the app can do is keep the HDR10+ metadata
away from the decoder.
When the switch is on, and only when a Dolby Vision decoder was actually
selected, the HDR10+ SEI messages (SMPTE ST 2094-40, recognized by their
ITU-T T.35 header `B5 003C 0001`) are masked in each access unit before
it is queued to MediaCodec. The SEI payload type byte is overwritten
with an unused value (254), so the decoder skips the message. That is
one byte rewritten in place per message: the masking filter returns the
size it was given, so the access unit is queued exactly as long as it
came out of the extractor, which is what makes it safe with both the
synchronous and asynchronous MediaCodec adapters. media3 does the same
thing for HAGC metadata in `HevcSeiUtil`; this filter only targets ST
2094-40, so other T.35 SEI such as CEA-708 captions are not affected.
The filter was written from the H.265 spec, no Kodi or media3 code was
copied.
The hook is generic rather than HDR10+ specific, as asked in the thread.
`BitstreamFilteringCodecAdapterFactory` wraps the codec adapter with a
`List<BitstreamFilter>`, and a filter is `(ByteBuffer, offset, size) ->
Int` where the returned `Int` is the new size of the access unit: the
adapter queues the size the chain reports, and hands each filter the
size the previous one reported. A filter which only overwrites bytes
returns the size unchanged, which is what the HDR10+ masking does; a
filter which drops NAL units compacts what it keeps and reports the
shorter size. A reported size which is negative or larger than the one
the filter was given is ignored rather than queued. That is what lets
the Dolby Vision profile 7 to 8.1 conversion of #244 arrive later as one
more filter instead of a second copy of the adapter plumbing.
When the switch is off, the playback path is unchanged. On a device
without a Dolby Vision decoder (or a display without DV support) the
wrapper is never installed, so HDR10+ only devices keep their metadata.
Encrypted samples, codec config buffers and empty buffers are passed
through untouched.
Kodi has an equivalent option for the same reason: xbmc/xbmc@032cbfeb
and xbmc/xbmc@e2c54e7f.
### Related issues
Implements what was requested in #1855. The filter list is what #244
(Dolby Vision profile 7 direct play) needs, so that conversion can be
added as another filter. #1895 implemented the same masking
independently and was closed in favour of this PR; the device profile
part of it was not carried over, see the note under Testing. Upstream:
androidx/media#1895, where the maintainers say the bug is in the device
codecs and that they don't plan a workaround in ExoPlayer itself.
## AI or LLM usage
The implementation (bitstream filter, MediaCodecAdapter wiring, unit
tests) were written with AI assistance (Claude). I reviewed the changes
and validated the behavior myself on a real MediaTek TV (Philips
55OLED810), with the setting on and off, on hybrid and DV-only content.
---------
Co-authored-by: Jycreyn <github.2ilp7@passmail.com>
## Description
Fixes the home page reloading from scratch when the app is resumed
This happened because the `userDto` is updated on app resume and since
it has a last accessed timestamp, it would always different and trigger
a reload.
### Related issues
Bug introduced by #1981
### Testing
Emulator
## Screenshots
N/A
## AI or LLM usage
None
## Description
When clicking on an episode, the app assumes if there is a season ID for
the episode, then there must be a series ID. However, on my JF 12.0
server, an episode (with no metadata) appeared on my Continue Watching
and it has a season ID (which in turn has a series ID) but no series ID.
I'm not sure if this really a valid situation or if it's a server bug,
but Wholphin shouldn't crash at least. So this PR checks both `seasonId`
and `seriesId` and if either is null, falls back to the generic "Episode
Page". Similarly, if a season has no `seriesId` (again, not sure if a
valid situation), a crash won't occur, however there is no generic
"Season Page", so an error page is shown.
### Related issues
TODO, check JF server issues, maybe fixed in 12.1?
### Testing
Emulator
## Screenshots
N/a
## AI or LLM usage
None
## Description
There is a race condition when switching users between
`HomeViewModel.init` and `HomeSetttingsService.loadCurrentSettings`.
This happens more often if the new user doesn't have a customized home
page. The result is that the previous user's home page settings are used
for the initial load of the home page.
This PR fixes this by collecting the flows for current user & home
settings, waiting until both are valid and refer to the same user, then
fetch the data.
### Related issues
Fixes#1976
Maybe related (and fixes) #1980, but need to investigate more
### Testing
Emulator. It's easy to reproduce by adding a manual `delay(2.seconds)`
at the start of `HomeSetttingsService.loadCurrentSettings`.
## Screenshots
N/A
## AI or LLM usage
None
## Description
Makes the `itemId` parameter optional allowing just passing
`serverId`/`userId` to switch the user and load the app at the home page
for that user.
Dev note: I added a new assert function `assertIs` which asserts that an
object `is` of a type. It also has a contract so the compiler will
smart-cast usages after the `assertIs`. Checking a result type is a very
common pattern in the unit tests, so this assert will simplify a lot of
current & future tests.
### Related issues
Closes https://github.com/damontecres/Wholphin/discussions/1824
### Testing
Emulator and added unit tests for `parseIntent`
## Screenshots
N/A
## AI or LLM usage
None
## Description
Fix two bugs:
- Favorites page didn't query for favorite people, introduced by #1864
- Fixes restoring focus on the search page after clicking a discover
item and going back
### Related issues
N/A
### Testing
Emulator
## Screenshots
N/A
## AI or LLM usage
None