Conversation
The structs every screen and the player consume are Spool's media model, not a Jellyfin artefact. They now live in src/media/MediaTypes.h, in the core (shell) source group. The two Jellyfin-only types move to the provider side: AuthSession to src/api/JellyfinSession.h and DiscoveredServer to src/discovery/DiscoveredServer.h. SeriesAudioSelection is playback policy, so it joins the player group. No field changes.
src/provider/PlaybackSource.h is the whole surface the player used from JellyfinApiFacade: media request headers, the TLS origin, the parallel request budget, trickplay tile URLs, series episodes for queue continuation, the three playback reports, and the two signals that invalidate a prepared mpv. The facade implements it; PlayerController, PlayQueueController and PlaybackReporter take the interface. The X-Emby-Token header the player used to build itself now comes from the Jellyfin side, so nothing under src/player includes src/api any more.
configurePlatformPlaybackCapabilities() took the Jellyfin facade so the webOS probe could push its codec list straight into the device profile. It now takes a std::function applier; main.cpp supplies the one that reaches the facade. The platform layer no longer includes src/api.
Shared shell and page QML assumed the Jellyfin provider was there: the top bar always drew a SyncPlay button, the shell always built a remote now-playing bar, and Session, RemoteControl, SyncPlay and Management were read unconditionally. Add a ProviderCapabilities singleton with one flag per optional capability and read each provider-shaped singleton only behind its flag, so a source without the capability has no such control and none of its bindings ever run. Every flag is true for now; the Phase 3 C++ registry replaces the file under the same names. The top bar's index space moves into TopBarNavigation.js so a gated-out button drops out of the Left/Right sequence instead of leaving a dead index, and a QML test pins both the capability names and that arithmetic.
…bridge SettingsController no longer holds the Jellyfin facade. It emits the playback preferences, the remote-control target flag and the updated user configuration, asks for its remote half with remoteLoadRequested(), and takes cultures and user configuration back through two apply methods. JellyfinSettingsBridge in src/api does what the controller used to do against the facade, including the once-per-session load guard and the signed-in checks. AppController wires the pair together.
A Provider declares which optional capabilities its media source serves; the registry holds the active one and publishes its flags as the eleven booleans shared QML already gates on. Nothing uses them yet.
The login flow, the remote control page and its menu and now-playing bar, the SyncPlay menu and the management dialog only exist for the Jellyfin provider. Give them a directory of their own so the provider can take them with it when it moves out, and point the shell, the overlay chrome and the route stack at the new place. Same QML module, so type names resolve as before.
tools/check-module-seam.sh fails when any core file (the platform, player and shell groups, or anything under the core directories) includes src/api or src/discovery, and runs as the module-seam ctest. Its --strict mode also counts SPOOL_JELLYFIN_SOURCES files as provider; that still reports the composition-root leaks through AppController, ArtworkService and AccountProfile that the provider registry work removes, so CI runs the default. LibraryQuery, SpoolLinkCodec, SpoolRemoteProtocol, LibraryListModel and MovieGridModel include nothing from the provider and move into the shell group.
JellyfinProvider owns the facade, LAN discovery and its cached server list, the session, QuickConnect and remote control, and builds SyncPlay, library management and the settings bridge once the app hands it the core objects they sit on. It registers its own QML singletons and keeps the session-driven lifecycle of its parts; main.cpp activates it in the ProviderRegistry, whose ProviderCapabilities instance replaces the QML placeholder singleton. The server and sign-out entry points move from App to Session, where the state they act on lives.
The signed-in-as, switch-profile and sign-out rows are declared with withAuth() and left out of the schema model when the active provider's capabilities lack auth; SettingsController reads that from the ProviderCapabilities instance main.cpp hands it.
…ehind interfaces
The content, home, search, prefetch and user-item-state controllers took
the Jellyfin facade and used a dozen of its methods. They now take
Catalog, SearchSource and UserItemStateSink, plain abstract classes that
carry exactly that surface, and ArtworkService asks an ArtworkSource for
image URLs instead of building Jellyfin's Items/{id}/Images path itself.
The facade implements all four beside PlaybackSource; one signedIn()
answers every interface's readiness question, and libraryScopeKey()
replaces the home controller's hand-rolled server/user cache key.
Tests render artwork requests through a recording source so they check
what the service asks for; the Jellyfin URL shape stays covered by the
facade URL test.
…oller PlatformApplicationServices takes an ApplicationHooks object (player, settings, a memory-pressure callback and the two forwarded signals) and RenderBenchmark a RenderBenchmarkHooks struct, so nothing under src/platform or src/diagnostics names the composition root. main.cpp fills both in.
With the catalog, search, item-state and artwork surfaces behind provider interfaces, the content, browse, home, search, prefetch and user-item-state controllers, the artwork service and its image provider and prefetcher, and the saved-account row no longer include anything from the Jellyfin side. Move them from SPOOL_JELLYFIN_SOURCES to SPOOL_SHELL_SOURCES and list the four new interface headers with them. AccountProfile is a generic saved-account row (server, user, token, avatar, timestamps) that core's profiles table already persists, so it goes with core rather than staying provider-shaped by name alone.
…r of Jellyfin Provider now hands out every interface the app consumes: catalog(), artwork(), search(), itemState(), plus the new StreamQualityControl, GroupPlayback and RemotePlayback, and carries the session-shaped hooks and signals (ready, restoreFromStorage, setDeviceId, setLocale, handleUnauthorized; sessionStarted/Ended, busyChanged, errorOccurred, toastRequested, contentChanged) the app used to take from the Jellyfin session and its controllers directly. Catalog gains fetchLibraries, fetchLibraryFilterOptions and fetchItemsByIds; PlaybackSource gains resolvePlayback (was negotiatePlayback) and fetchMediaSegments. The transcoding ceiling moves behind StreamQualityControl with its own streamQuality capability, and the player overlay only offers the quality menu when it is set. SyncPlayController implements GroupPlayback, RemoteControlController implements RemotePlayback, and JellyfinProvider relays session and remote-command signals, sets the artwork token, runs the deferred post-login work and publishes playback activity itself. AppController takes a Provider, includes nothing from the Jellyfin side, guards every group and remote path against a provider that has neither, and moves into the core source group.
…r at start-up LocalProvider (src/providers/local) serves every media file under one directory as a single library: browse pages, details, latest, search, in-memory favourite/played/resume state, and a playback session whose URL is the file itself. It has no sign-in, no artwork, no segments and no reporting, and announces its session from restoreFromStorage() since there is nothing to wait for. It is the second implementation of the provider contract and stays as its permanent fixture (local-provider ctest against tests/media/fixtures). main.cpp registers both providers and activates one from --provider or SPOOL_PROVIDER (default jellyfin); the local one takes --library-root or SPOOL_LOCAL_LIBRARY. With nothing passed the Jellyfin path is unchanged. The router now starts on home when the active provider has no auth, so the provider's login page is never built without its singletons. The seam check treats src/providers as provider code.
The plan the provider seam work follows, through the phases that have landed and the ones that have not: the two-repo shape, the capability contract, the picker and runtime download path, and the directory move and extraction still to come.
Classify routes before synchronous Loader work and guard reentrant promotion. Replace stale per-step singleShots with an owned deadline, record failures and runtime context, and reject incomparable reports. Timing schema 2 requires new baselines. Validated Python report regressions and five actual route-function scenarios with Node; the original route code fails the cold-classification regression. Qt test registered but not run: Qt 6.11 SDK is unavailable in this environment.
mpv reports a clean end of file whenever the demuxer runs out of data. A network stream that drops and cannot be reopened (a failed HLS segment run or a refused range reconnect) ends the same way, so Spool reported the item as finished at its full runtime, marked it played and started the next episode. Only an end within ten seconds of the known duration now completes the item. An earlier end is an interruption: the position is kept as a resume point, and playback picks up there if the attempt made progress, so a source that keeps ending early cannot restart forever.
… check The settings schema test re-asserted spec fields, display labels, titles and the absence of long-removed specs, none of which can regress without a deliberate edit. The persisted key inventory, defaults and normalisers stay. The provider package test read a Jellyfin archive that was replaced on 26 September and skipped itself when the file was missing, leaving the compressed-block decoder untested. It now finds the archive through providers/lock.json and requires it.
A sign-in code now says what to do in full-size text above it instead of a caption inside its box. The link screen takes a linkUrl: a desktop opens it once a code is ready and offers it again as a button, a phone shows it as a link, and a television spells it out. The code sits beside a Copy button wherever there is a clipboard, a waiting line shows the screen is listening, and small print such as trademark notices goes to a footnote. Add a provider gets page margins, a subtitle, rows with large icons, names and summaries (official providers included), and buttons named for what they do. This computer gets a computer icon instead of its initial. Password sign-in drops its duplicate Spool/service row for a title that says what to do.
filterOptions may name the BrowseFilters a library honours. The filter box then offers only those and drops sections left empty, so a server that ignores a filter no longer shows one that silently does nothing. HDR (isHdr) is offered only where declared; no server answered it before. isHdr counts as an active filter, which also keeps filtered pages out of the unfiltered page cache. The panel is wider and taller, and its title says how many filters are on.
Every official provider ships in the package again, pinned to its published release asset, so a fresh install works offline from the first launch. Builds with open or curated provider sources still install newer store releases over the bundled ones.
…pv loader webOS loads libmpv after the first frame and reaches it only through WebOSMpvRuntime's forwards. Local thumbnails started calling mpv_render_context_get_info in 8b7f60f, which had no forward, so the webOS app no longer linked.
A provider could mark a subtitle external but had no way to say where it was, so a sidecar .srt listed beside a directly played file could never be shown. A Stream may now carry a url. The player adds each one to mpv once the file loads, without selecting it, and orders the session's streams as mpv lists them: the file's own tracks, then the added files. A subtitle file on another origin would carry this stream's credentials there, and one without a url cannot be fetched; both are left out rather than shifting every track after them.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.