Repository navigation
Windows desktop app: browse, extensions, mpv playback, and downloads - #3262
weaponlions wants to merge 120 commits into
Conversation
- Fix the icon path (icons live in src/desktop-icons) - Take packageVersion from versionName instead of a hardcoded 1.0.0 - Set a fixed upgradeUuid so new versions upgrade in place - Add Start menu entry, desktop shortcut and install folder picker - Bundle the JDK modules the app needs (java.net.http etc.) - New desktop.yml workflow: runs library jvmTest and packageMsi on windows-latest, uploads the MSI Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: Windows MSI installer and CI job
- JvmPreferenceStore keeps every preference in one JSON file under %APPDATA%\CloudStream (or ~/.config, ~/Library/Application Support), written atomically; a corrupt file falls back to defaults and is kept as settings.json.corrupt - AppDirs resolves the data folder, overridable with -Dcloudstream.home - DesktopLog copies stdout/stderr into logs/cloudstream.log with timestamps and keeps the previous run as cloudstream.old.log - SavedWindowState restores window size, position and maximized state, ignoring positions on monitors that are no longer connected - Unit tests for the preference store, run in the desktop CI job Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: save settings, log to a file, remember the window
- AppShell with a navigation rail (Home, Search, Library, Settings); Ctrl+1..4 switch destination, Escape / Alt+Left go back - Desktop settings screens (General, Player, Layout, Providers) built on the shared Mihon preference widgets and AppSettings keys, leaving out Android-only options (TV, gestures, battery, rotation, PiP, APK) - Theme and primary color follow the settings live - English strings and vector icons copied from app/res into the shared compose resources, keeping the Android names Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: navigation rail and settings screens
- PluginLoader loads the cross-platform .jar that extensions publish with isCrossPlatform = true; the jar has no manifest.json, so the plugin class is found by its @CloudstreamPlugin annotation. Unloading removes the plugin's providers and extractors and closes the class loader so Windows can replace or delete the jar - RepositoryClient reads repo.json and plugin lists (including jarUrl, jarHash, jarFileSize) and verifies size and SHA-256 of downloads. It uses OkHttp because the JDK HTTP clients only try the first resolved address, and one GitHub address is unreachable on some ISPs - DesktopPluginManager installs into <app data>/plugins, keeps installed.json, loads everything at startup in the background and records per-plugin load errors - Settings > Extensions: add/remove repositories (one click for the official one), install/update/uninstall, Android-only plugins listed as such - Tests: annotation scan, load/unload, list parsing, install/restart/ uninstall and hash rejection against a local HTTP server, plus an opt-in online test (CLOUDSTREAM_ONLINE_TESTS=1) that installs a real plugin from the official repository and searches with it Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: install and load extensions
Shared MVI view models for Search and Home in shared/commonMain, following COMPOSE.md, with the desktop screens built on them. - Search queries every enabled provider at once and fills each provider's row as it answers. Failures are listed separately. Rows with more pages get a More button. Ctrl+F opens Search. - Home shows one provider's home page, picked from a menu. The choice is saved under home_api_used and falls back to the first provider when that one is removed. Home refreshes when plugins finish loading or the provider settings change. - Providers are filtered by the same language and media-type settings as the Android app, and hidden search qualities are dropped. Provider calls use the same timeouts as APIRepository. - Posters load through Coil with OkHttp and a disk cache in LOCALAPPDATA, sending the provider's poster headers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: Search and Home screens
Clicking a poster opens its details. Play finds the links and plays the best one in the app window with libmpv. - Player: libmpv over JNA, embedded in the window. libVLC was tried first but can only send a Referer and User-Agent, while many extensions need other headers too. mpv sends every header the extension gives on every request, playlists and segments included. Plays MP4, HLS and DASH with hardware decoding. mpv's own controls handle seeking. - libmpv-2.dll comes from a pinned shinchiro build, downloaded at build time, checked against its SHA-256 and installed with the app together with a third-party notice and the GPL text. - Shared DetailViewModel: movie, series and anime pages, seasons grouped with their names, sub/dub choice, torrents marked unsupported. - Shared LinksViewModel: links stream in, best quality first. The best link plays once all are in, or 6 s after the first if extractors are slow. A link that fails to play moves on to the next. Torrent and DRM links are skipped and counted. - Player screen: a bar with Source, Subtitles and Audio pickers that open beside the video, since nothing can be drawn over the native video window. Extension subtitles are downloaded with their own headers. Space, arrows, F and Escape work, inside the video too. - Navigation: details and player open as pages on top of Home or Search, each owning its view models. Full screen hides the rail; Escape leaves full screen, then the page. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: detail page and video playback
The Add repository box takes what Android takes: a URL, a cloudstreamrepo:// or https://cs.repo/ link, or a short code such as "megarepo", looked up at cutt.ly (or py.md for "!code") like RepositoryManager.parseRepoUrl. MegaRepo holds one plugin, MegaProvider, an Android helper that adds every repository in recloudstream/cs-repos through app internals desktop does not have. So when a short code or URL leads to MegaRepo, the app adds those community repositories itself. Opening MegaRepo explains this and offers the same, and the Extensions screen has an "Add all community repositories" button. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: repository short codes and MegaRepo
- Added DexConverter for converting Android extensions (.cs3) to JAR files for desktop compatibility. - Introduced MissingMembers to handle missing methods in desktop versions of Android classes. - Created StubGenerator and StubModel to generate stubs for Android classes with missing members. - Established StubHierarchy to define the superclass relationships for stubbed Android classes. - Developed AndroidExtensionsTest to validate the functionality of Android extensions on desktop, including conversion and search capabilities.
- Extensions is now a menu of three pages: Repositories, Installed extensions and Add repository. - Repositories are listed by the name in their repo.json, kept so the list shows them right away, or "Repo (owner)" until it is known. - Installed extensions checks their repositories for new versions, with Update and Update all, and updates them at startup unless turned off. - Settings has Updates: the app finds newer releases on GitHub, checks the installer's SHA-256 and installs it with msiexec. It also asks at startup unless turned off. - Settings has Storage: clear the cache, remove all extensions or erase everything, done on a restart because loaded extensions keep their jars locked. - After CloudStream is installed again, it asks whether to keep the old data or start fresh. Updates made through the app do not ask. - Stubs of the app's sync providers extend SyncAPI as in the app, which CineStream needs to load. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: extension pages, updates and fresh start
- Library tab: titles in Watching, Plan to watch, On hold, Completed or Dropped, set from Add to library on a title's page. - The player saves how far the user got and starts there next time; a new source carries on from the same point. - Home shows Continue watching, which moves on to the next episode once one is nearly finished, like the Android app. - Title pages show Resume with the time, and each episode's progress or that it was watched. - Saved in the Android app's DataStoreHelper keys and JSON, with its title and episode ids, so a later backup import works either way. - Remove all extensions keeps the app's data store, which now holds the library. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: library and resume watching
Recent searches on the Search page, stored in the Android app's search_history keys. Extension toasts (android.widget.Toast and CommonActivity.showToast) show as a message in the app window. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: search history and extension toasts
- A control bar under the video: play/pause, -10/+10 s, a seek bar with the time, mute, volume and full screen. In full screen the bars show while the mouse moves and hide after 3 s. mpv's own controller is off: embedded, mpv gets no mouse input, so it never worked. - The mouse over the video is handled on the canvas: a click closes an open panel or pauses, a double click toggles full screen, the wheel changes the volume. Escape closes an open panel first. - Source names show in full, in a wider panel. - Subtitles: the picked subtitle is added again when the source changes, extension subtitles are matched to mpv tracks by file, sorted by language, numbered when a language repeats and show their host. A Timing control moves them earlier or later. New Speed panel. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: easier player controls, panels and subtitles
- Loading and buffering show as a spinner with text drawn by mpv over
the video: finding links, opening the file, seeking, or the cache
running empty ("Buffering 45%").
- Dark player bars in every theme. A thin seek bar across the top of
the controls shows what is played and what is loaded, thickens under
the mouse and shows the time under it. Icon buttons for 10 s back and
forward, mute and volume; Subtitles, Audio (with the track count),
Speed and Source (with the quality) on the right.
- The Audio button is always there. Tracks show their language in
full, channels, codec and the default; with one track the panel says
other sources may have other languages.
- A link that ends within seconds of starting counts as failed and the
next one plays, and progress is only saved once a link really plays,
so a broken file no longer marks an episode watched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: player redesign, buffering spinner and audio picker
- The player's keys go through the window before anything else, so they work whatever has the focus. In full screen the bars hide with the button that had the focus, which left Space and the arrows doing nothing. The window's own shortcuts ran first too, so Escape with a panel open closed the player instead of the panel. - Escape closes an open panel, then leaves full screen, then goes back. K, J and L work like Space and the arrows. Keys that change something show the bars in full screen. - Real full screen on Windows: the window drops its title bar and border and covers the monitor, then gets them back. Compose's full screen kept the title bar. A maximized window is restored first and maximized again after, so it does not come back under the taskbar. - The mouse's back button goes back everywhere, the video included. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
M pauses the player and hides the whole window, gone from the taskbar and Alt+Tab. A tray icon, shown only while hidden, brings it back as it was. Typing an M into a text field still types it: the text fields' input sessions are tracked, since their letters don't come as key events. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: predictable player keys, full screen and back
Desktop: M hides the app and pauses the video
…from servers that cannot seek mpv starts a file at the position it is given, but only when it can jump into the file as it opens. A server that ignores range requests does not let it, and the new source started from the beginning. Once the file plays, the player checks where it started and seeks there if it was more than 5 s short. Each resume is logged, with whether the stream was seekable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop player: switching source resumes, also from servers that cannot seek
…ing that scrolls - The title page has a Search button: it opens Search with the title typed in and searched, to find it in the other extensions - Continue watching is part of the home page's list, so it scrolls away with the rows instead of staying at the top - Its cards are small, 84 dp high: a thumbnail, the title, the episode and the minutes left, the progress, and an x to drop it Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e, bigger headings - Posters and Continue watching cards grow 6 % under the mouse, over their neighbours, with a shadow - Rows show arrows at their ends while the mouse is over them, which scroll by most of the row's width: a mouse cannot swipe - The default dark theme is neutral and darker, after Netflix: the background #141414, surfaces a step lighter, gray text #B3B3B3. The other themes keep the Android app's gray text - Row titles are bold and larger, and the rows have 32 dp margins Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Home opens on the provider's first title, large: its backdrop faded into the page, its name, year, length, genres and the start of its story, and More info. The rows only have posters, so the rest loads from its page, the poster standing in until then - The side bar is 72 dp, in the page's colour, icons only: the current page is lit and marked with a bar, and each icon names its page on hover Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sky-blue accent Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop: Search button on title pages, smaller scrolling Continue watching
Desktop UI: hover zoom, row arrows, darker theme, bigger headings
Desktop UI: featured banner on Home, slim side bar
Desktop UI: dark theme in Prime Video colours
…g plays at once from the last source Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd age rating Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop UI: floating glass dock, Continue watching plays directly
Desktop UI: Home banner uses the extension's logo, score and age rating
…s header The window's title bar and border are painted in the theme's colours through DWM (exact colours on Windows 11, dark title bar on Windows 10), again whenever the theme changes. The player's bars and side panels use a dark theme's own colours, neutral greys under a light theme. The settings header takes the page's colour. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop UI: the theme on the window frame, the player and Settings
…ar and compact downloads - Light theme in the Prime colours (white, cool blue-gray, deeper sky blue); "Dark" is now the Prime navy and the default, the pure black one is named "Black"; the Normal accent swatch shows the theme's real accent - Settings: a readable centred width, each group as rounded cards in the theme's box colour, flat switches and a slider without step dots, the header the page's colour also when scrolled, the info line with its icon beside the text - Home: the provider and Reload in a floating glass pill like the dock, the provider list a rounded themed menu with the current one ticked and a filter box past 8 providers - Downloads: one compact card per title, a movie on a single line - Title page: the backdrop behind the header, a shadowed poster, the score, rounded episode lines; Search's box rounded; the glass shared by the dock and the Home bar, with a dark edge on light themes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop UI: Prime-style light theme, settings cards, glass Home bar, compact downloads
…a clean hover on light themes - Posters cast their hover shadow from the picture alone, so the title under them no longer sits in a grey box on a light theme; the row arrows are round buttons over a soft fade - Buttons are pills: the main action in the theme's accent, the others in its tonal colour, dialogs too - Title page: the backdrop across most of the window with the poster, logo or name, facts with the score in the accent, genre pills and a story that expands; Play (or Play S1 E1 for a series not started), Start over, the library, download, and round tools for other sources, the trailer, the provider's site and copying the link; how far it was watched; episodes as a grid of cards with progress and a play sign on hover; the cast; More like this; and an About card with everything known about the title Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop UI: fuller title page, accent pill buttons, clean hover on light themes
A file Windows would not delete for a moment (the player or a virus scan holding it) was left on the disk for good, its record already gone. It is tried again for a few seconds, then deleted when the app closes. Folders a deleted download leaves empty, such as a series' folder after its last episode, go too; the downloads folder stays. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Downloads: deleting a download cleans up safely, even a file held open
- Desktop Build checks pull requests only, not the merge again; a new push cancels the check of the commit before it; the installer is built only when run by hand; time limit 40 min - Release runs only when asked (Run workflow on master, or a v tag), no longer on every merge, and links to the libmpv release for the dll's source instead of uploading the same 462 MB with every release - libmpv (about 70 min a build) runs only by hand, not on pull requests that touch it Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CI: spend far fewer GitHub Actions minutes
…le again there A seek past what is loaded asks the server for that part of the file. Some file hosts never answer that request, and mpv then waits at the new spot for good, the spinner turning: mpv's network-timeout does not end a stalled transfer. After 12 s of waiting the file is opened again at that point, on a new connection, with the chosen audio track and subtitle, at most twice per seek. A slow host that does answer (8 s in tests) is left alone. mpv's errors now go to the app's log, so a refusing server shows there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop player: a far seek that the server never answers opens the fi…
|
You have created a very cool project, but it is not something I can merge. Claude is very good at creating demos, or something which works on your own machine. It is seemingly not good at creating maintainable or portable code. This is visible in two core issues of this code: Inherent Windows dependency and lots of duplicated code. The entire AndroidCompat will require duplicated effort when changing anything and invariably cause weird issues. The heavy Windows dependency removes all upsides of working with Compose. We are aiming to make CloudStream platform neutral using KMP and target many different platforms. We are working on making CloudStream Windows-compatible, but taking shortcuts (such as Windows-only, or duplicated code) in the official project will slow down the project too much in the long run. |
|
Thanks for taking the time to review my PR and explain your concerns. I understand your points regarding code duplication, Windows-specific dependencies, and the importance of keeping CloudStream platform-neutral and maintainable. I’m still very interested in contributing to CloudStream, and I’d really appreciate an opportunity to work on the desktop version. I’m happy to start with any bugs, smaller features, or improvements that align with the project’s architecture and KMP goals. I’m also willing to rework my approach and learn from the project’s existing patterns to ensure my contributions are maintainable and portable. I understand if this PR isn’t suitable for merging in its current state, but I’d be grateful for a chance to contribute in a way that genuinely benefits the project. If there’s an issue you think would be a good starting point, I’d love to take it on. |
|
That's great to hear! CloudStream would really benefit from more help! Currently the desktop compose migration requires a lot of precision to get right, in an admittedly confusing code base. This makes it difficult for new contributors to help. However, this will only get easier in time as more of the difficult stuff is solved. I would suggest looking into how smaller bugs or features can be implemented within the existing compose rework (currently only Settings). This makes it possible to learn the code base and the review process, while working with compose. An example of just a feature like that would be a custom DNS: #3200 #1708 If you want to work on something else, you have a wide selection to pick from in https://github.com/recloudstream/cloudstream/issues, but I would advise against implementing new features, since it is very easy to make mistakes when creating new features in a new code base. |
Summary
This adds a working Windows desktop version of CloudStream, built on the existing Compose Multiplatform
desktopApp/sharedmodules. It runs the same extensions as Android: it downloads them from the usual repositories, converts them for the JVM, and runs them. Video plays through libmpv.63 commits; 190 files changed (+19,016 / −152). Almost all of it is new desktop-only code. Changes to shared code are small and listed under "Changes outside the desktop app".
Features
Browsing
Extensions
Player (libmpv embedded in the window)
Network
Downloads and torrents
Settings and appearance
%APPDATA%\CloudStream\logs.Changes outside the desktop app
settings.gradle.kts: adds the:androidCompatmodule.androidCompat/(new): JVM stand-ins for the Android classes that extensions use (Context,SharedPreferences,Activity,Log, …), plus the dex converter and stub generator.library/:DesktopWebViewinterface.WebViewResolver.jvm.ktuses it when the desktop app installs one; otherwise behaviour is unchanged.MainAPI.kt: a bug fix inEpisode.addDate. A date with a time but no time-zone offset was read as UTC; it now uses the device's time zone, as the code comment already said it should. This affects Android too.shared/: view models (home, search, detail, links), stores for the library and settings, icons and strings, and small theme changes.gradle/libs.versions.toml: new desktop dependencies.Build and run
libmpv is built from source by
.github/workflows/libmpv.yml(Clang, same setup as shinchiro's builds). The complete source of every bundled library is published with the release, to meet the LGPL.CI and release
desktop.yml: builds the MSI and runs the JVM tests.release.yml: publishes a beta of the Windows app on every merge into master. The app has a built-in updater that checks those releases.libmpv.yml: builds libmpv and its source archive.To decide before merging
desktopApp/build.gradle.kts) and the updater (AppUpdater.REPOSITORY) still point toharsh-pockt/cloudstreamreleases. They need to point to this repo's releases, which means runninglibmpv.ymlhere once.Known limitations
Testing
shared/src/jvmTestcover plugin loading and conversion, downloads, the watch store, subtitle settings, preferences, the HTTP client, the system browser and TorrServer.