Skip to content

Add in app download functionality - #1404

Merged
nielsvanvelzen merged 39 commits into
jellyfin:masterfrom
7ritn:download
May 7, 2025
Merged

Add in app download functionality#1404
nielsvanvelzen merged 39 commits into
jellyfin:masterfrom
7ritn:download

Conversation

@7ritn

@7ritn 7ritn commented Jun 3, 2024

Copy link
Copy Markdown
Contributor

This PR internalizes the download process.

Changes
Downloads are stored inside the app directory including thumbnail and external subtitle files. MediaSource data is downloaded as well and stored inside the Room Database.
A new Downloads fragment is added to show available downloads. Downloads are played back using the Exoplayer.

What's left
This is not a complete list

  • better download overview
  • add functionality to remove downloads
  • add button in main menu to get to downloads (currently only available from server selection)
  • overall polish

Notes
There is still lots of work to do, but it is already working. I new to Android development, so I am really sorry if something is not correct. Furthermore I implement this functionality as part of my Master's degree, so even if for whatever reason this PR is rejected, I will still complete the project to my best ability. I sorry that I changed some ExoPlayer code even though, it is currently reworked, but due to time constraints, I could not wait.

Thanks for any feedback :)

@jellyfin-bot jellyfin-bot added this to the v2.7.0 milestone Jun 3, 2024
@7ritn

7ritn commented Jun 4, 2024

Copy link
Copy Markdown
Contributor Author

Updated download page so it looks somewhat nice. Also added functionality to delete downloads.

@7ritn

7ritn commented Jun 6, 2024

Copy link
Copy Markdown
Contributor Author

So I would call the PR in a beta state. Feature wise, I think it is already at a good point. Adding the view downloads button requires modifying the web app. If something is missing I can still add it, but otherwise next step would be the polish.

@rcv11x

rcv11x commented Jun 6, 2024

Copy link
Copy Markdown

Thank you very much for this PR, it looks very good, I have a few questions to ask you:

1 - This is to download media on the device and watch them locally without internet?
2 - When you download the media are they compressed so that they take up less space on the device or are they downloaded with the actual size they occupy? ❤️

@7ritn

7ritn commented Jun 6, 2024

Copy link
Copy Markdown
Contributor Author

Thank you very much for this PR, it looks very good, I have a few questions to ask you:
1 - This is to download media on the device and watch them locally without internet?

Yes. Currently downloads are saved using Android's built-in DownloadManager. They are saved in for example the Downloads folder so one can playback the file using VLC. This PR adds the functionality to save the download to the app internal storage making playback from within the app possible.

2 - When you download the media are they compressed so that they take up less space on the device or are they downloaded with the actual size they occupy? ❤️

Video files are usually already very well compressed through the used codecs, adding a conventional lossless compression such as zstandard would barely reduce the size. The next step after this PR could adding functionality to support downloads in different bitrates, enabling smaller downloads.

@7ritn

7ritn commented Jun 24, 2024

Copy link
Copy Markdown
Contributor Author

While thinking about implementing the ability to download transcoded media (streams), I realized it would be better if ExoPlayer would handle downloading, since it natively supports HLS streams, making future efforts easier. So I rewrote the download logic to utilize ExoPlayers in-buildt download capabilities.

Oh yeah this also makes regular video playback use the downloade / cached files if available.

Comment thread gradle/libs.versions.toml Fixed
Comment thread gradle/libs.versions.toml Fixed
Comment thread gradle/libs.versions.toml Fixed
Comment thread gradle/libs.versions.toml Fixed
Comment thread gradle/libs.versions.toml Fixed
Comment thread app/src/main/res/values/strings.xml Fixed
Comment thread app/src/main/res/values/strings.xml Fixed
Comment thread app/src/main/res/values/strings.xml Fixed
Comment thread app/src/main/res/values/strings.xml Fixed
Comment thread app/src/main/res/values/strings.xml Fixed
@7ritn
7ritn marked this pull request as ready for review June 26, 2024 08:30
@satmandu

Copy link
Copy Markdown

I would love to test this if you want to post some builds...

I have a flight coming up. 😅

@neBM neBM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much of the detekt issues blocking the PR seems like quick fixes except for one :/
Looks like we're the unlucky PR that needs that final param and hit the limit! 😆

I've thought of a potential solution but it's by no means perfect.

Comment thread app/src/main/java/org/jellyfin/mobile/player/source/JellyfinMediaSource.kt Outdated
@7ritn

7ritn commented Aug 20, 2024

Copy link
Copy Markdown
Contributor Author

Commit #7ce600c fixes the HLS content source error. Problem was that the exoplayer cache cached videos when playing them back, this was not intended and let to complication. The commit makes the cache read-only.

@7ritn

7ritn commented Aug 22, 2024

Copy link
Copy Markdown
Contributor Author

Commit #6db98b7 improves download notification when multiple downloads are in progress. This should fix all current outstanding problems.

@felixschndr

Copy link
Copy Markdown

Would it be possible for someone to take a look at this PR and review it please? That'd be great ❤️

@jellyfin-bot jellyfin-bot added the merge conflict Conflicts prevent merging label Jan 26, 2025
@Maxr1998

Copy link
Copy Markdown
Member

Sorry for taking so long to look into this. I'd like to get these changes in next. Would you mind rebasing this onto the latest master so that I can properly test and review it?

@jellyfin-bot jellyfin-bot removed the merge conflict Conflicts prevent merging label Jan 30, 2025
@7ritn

7ritn commented Jan 30, 2025

Copy link
Copy Markdown
Contributor Author

I currently can't really test changes rn. So I can't that any guarantees if it still works.
Is this conflict-resolving commit enough or do you want a proper rebase? I am not really used to having to rebase onto so many new commits lol Otherwise I think neBM had some proposal.

@satmandu

This comment was marked as off-topic.

@jellyfin-bot jellyfin-bot added the merge conflict Conflicts prevent merging label Mar 18, 2025
@7ritn

7ritn commented May 6, 2025

Copy link
Copy Markdown
Contributor Author

I have to be real, this feature is the most requested feature of the official Jellyfin project (https://features.jellyfin.org/?view=most-wanted). It is understandable that people lead busy lives, but if the maintainer is not able to look into this for almost a whole year, then maybe a solution needs to be found. It makes sense that there are many other Jellyfin Android apps with more features and better maintenance.

@satmandu

satmandu commented May 6, 2025

Copy link
Copy Markdown

@nielsvanvelzen would you be ok with an edge release with some of these long waiting PRs pulled in?

A quarterly or sooner unstable release cadence with lower expected polish, separate from the default ship it when it is ready schedule would help keep outside developers who want to collaborate interested...

@nielsvanvelzen

Copy link
Copy Markdown
Member

@satmandu I agree with you that the current situation is not ideal. If someone is willing to help out with one or all of these tasks we can proceed with merging:

  • Fixing the merge conflicts (ideally the author of course)
  • Reviewing the code
  • Testing the new functionality

Polishing and fixing smaller issues can be done in separate pull requests.

@7ritn

7ritn commented May 6, 2025

Copy link
Copy Markdown
Contributor Author

Sure I can look into this again. At least now I finished my semester abroad

@jellyfin-bot jellyfin-bot removed the merge conflict Conflicts prevent merging label May 6, 2025
@7ritn

7ritn commented May 6, 2025

Copy link
Copy Markdown
Contributor Author

Removed merge conflict, will now make sure everything works.

Comment thread app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/downloads/DownloadTracker.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt Fixed
Comment thread app/src/main/java/org/jellyfin/mobile/player/queue/QueueManager.kt Fixed
@Maxr1998

Maxr1998 commented May 6, 2025

Copy link
Copy Markdown
Member

I have to be real, this feature is the most requested feature of the official Jellyfin project (https://features.jellyfin.org/?view=most-wanted). It is understandable that people lead busy lives, but if the maintainer is not able to look into this for almost a whole year, then maybe a solution needs to be found. It makes sense that there are many other Jellyfin Android apps with more features and better maintenance.

I get you, the situation totally sucks. I probably shouldn't even have left the project for so long, because getting back into it only got harder because of that. The reality is that I simply have no time to actively maintain this app right now, and it pains me since I also had a lot of plans (including unfinished work like the player UI rewrite). The little time I can spend on it every few weeks to months usually goes mostly into housekeeping and basic maintenance aka dependency updates and such, but that may be the wrong approach. I'm honestly fine with pulling in this PR mostly unreviewed at this point, considering multiple community members already worked on it.

@nielsvanvelzen would you be ok with an edge release with some of these long waiting PRs pulled in?

A quarterly or sooner unstable release cadence with lower expected polish, separate from the default ship it when it is ready schedule would help keep outside developers who want to collaborate interested...

That was already my plan when I last talked to Niels, but that was probably weeks if not months ago, again. Having a download feature in the app would be amazing, and merging this and other bigger PRs would then allow us to iterate on the existing state without risking to break other open PRs again and again. So yes, absolutely agree.

Unfortunately, I really can't and don't want to promise anything right now. But I definitely hear and understand the frustration, and won't get in the way while trying to find a solution.

@7ritn

7ritn commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

Fixed cache not being properly used when downloading.

Tested:

- detekt builds successfully (when runnig gradlew detekt)

- watching an episode not downloaded using ExoPlayer while online with external and embedded subtitles
- watching an episode dowloaded using Exoplayer while online
	- uses download cache in DirectPlay
	- fast seeking
- downloading multiple episodes
	- progress notification for total download progress
	- individual success notification for each download
- disrupting download
	- when going offline, download is resumed when connected again
	- when Jellyfin is closed, the download service continues
- watching a downloaded episode while offline with external and embedded subtitles
	- seeking in the episode is instant
	- subtitle switching works seemless
- deleting episode from Downloads
	- episodes is removed from Available Downloads
	- cache is cleared

<string name="store_videos_in_internal_storage">Store videos in internal storage</string>
<string name="stored_videos_in_internal_storage_desc">Videos such as movies and TV shows will be stored in the app\'s internal storage. Allowing access from within the app.</string>
<string name="tv_show_desc">%1$s - S%2$dE%3$d</string>
<string name="downloading_desc">Downloading %1$d titles</string>

Check warning

Code scanning / Android Lint

Potential Plurals

Formatting %d followed by words ("titles"): This should probably be a plural rather than a string

@nielsvanvelzen nielsvanvelzen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving (& merging) for the sake of progression. I have not reviewed or tested these changes, I just looked over it briefly.

As mentioned in my previous comment, any issues with these changes can be resolved in separate pull requests. If you're interested in helping out please do so! We'd appreciate any testers/reviewers & developers. For questions please visit our Android chat via Matrix or Discord (listed at jellyfin.org/contact)

@nielsvanvelzen
nielsvanvelzen merged commit 215a97a into jellyfin:master May 7, 2025
@7ritn

7ritn commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

Just one thing I noticed that when downloading at lot of episodes at the same time, the total download progress tends to reset, when an episode finishes downloading. Could look into it the next few days.

@7ritn
7ritn deleted the download branch May 7, 2025 06:18
Comment thread app/src/main/java/org/jellyfin/mobile/data/DatabaseModule.kt
Comment on lines -147 to +160
val customPrefs = displayPreferencesDto.customPrefs

displayPreferences = DisplayPreferences(
skipBackLength = customPrefs[Constants.DISPLAY_PREFERENCES_SKIP_BACK_LENGTH]?.toLongOrNull()
?: Constants.DEFAULT_SEEK_TIME_MS,
skipForwardLength = customPrefs[Constants.DISPLAY_PREFERENCES_SKIP_FORWARD_LENGTH]?.toLongOrNull()
?: Constants.DEFAULT_SEEK_TIME_MS,
)
customPrefs = displayPreferencesDto.customPrefs
} catch (e: ApiClientException) {
Timber.e(e, "Failed to load display preferences")
Timber.e(e, "Failed to load display preferences from API")
}

displayPreferences = DisplayPreferences(
skipBackLength = customPrefs?.get(Constants.DISPLAY_PREFERENCES_SKIP_BACK_LENGTH)?.toLongOrNull()
?: Constants.DEFAULT_SEEK_TIME_MS,
skipForwardLength = customPrefs?.get(Constants.DISPLAY_PREFERENCES_SKIP_FORWARD_LENGTH)?.toLongOrNull()
?: Constants.DEFAULT_SEEK_TIME_MS,
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default display preferences should already use these defaults, so I'm not sure these changes are necessary..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.