feat(DispatcherClient): add VOD models + browse / detail / provider client methods (#17) — model + client - #131
Draft
Drvolks wants to merge 1 commit into
Draft
feat(DispatcherClient): add VOD models + browse / detail / provider client methods (#17) — model + client#131Drvolks wants to merge 1 commit into
Drvolks wants to merge 1 commit into
Conversation
…lient methods (#17) Implements the Dispatcharr /api/vod/ contract from the pinned spec on issue #17. Covers browse, search, movie detail, multi- provider list, series detail (with episodes), and proxy URL construction for playback. Scope of this PR (model + client method only — Linux-testable): - 7 Codable structs: VODLogo, VODCategory, VODItem (browse), VODPage, VODMovieDetail, VODEpisode, VODSeriesDetail, VODProvider. All snake_case CodingKeys pinned to match the Dispatcharr wire format. - 6 client methods on DispatcherClient: fetchVODCategories, fetchVODContent (paginated + search + category filter), fetchMovieDetail, fetchMovieProviders, fetchSeriesDetail, vodStreamURL (proxy URL with optional m3u_account_id / stream_id query params for multi-provider selection). - 17 unit tests covering decode, round-trip, missing optional fields, snake_case wire format pin, and the custom VODProvider init(from:) that flattens the nested m3u_account {id, name} sub-object. Linux validation: swift test passes 164/164 on Debian 13 / Swift 6.0.3 (147 from #119 + 17 new DispatcharrVODTests). Follow-up PRs (out of scope here, require Xcode / SwiftUI): - VODBrowseView / VODBrowseViewModel / VODItemCard: paginated grid with search + category filter (uses fetchVODContent) - VODMovieDetailView: provider-info display + play button (uses fetchMovieDetail + vodStreamURL) - VODSeriesDetailView: season picker + episode list (uses fetchSeriesDetail + vodStreamURL(contentType:"episode",...)) - VODEpisodeRow: episode row component for season lists - AppState.Tab: add .vod case under #if DISPATCHERPVR - NavigationRouter: add VOD tab to iOS / tvOS / macOS variants All test fixture UUIDs are nil-UUIDs (00000000-...-0000) — no real user or server data.
Drvolks
marked this pull request as draft
August 13, 2026 20:34
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.
Summary
Implements the Dispatcharr
/api/vod/contract from the pinned spec on issue #17. StreamClient can browse VOD categories and a unified movie+series list, fetch movie detail (with multi-provider info), fetch series detail (with episodes), and build the proxy URL for playback. Pure-Foundation data layer + client method only — the SwiftUI browse/detail views land in follow-up PRs.What's in this PR
NexusPVR/Core/Models/DispatcharrVOD.swift— 8Codablestructs:VODLogo—id/url/cache_url(snake-case pinned)VODCategory—id/name/category_typeVODItem— the unified/all/browse shape (id/uuid/name/description/year/rating/genre/duration/content_type/logo)VODPage— paginated response wrapper (count/next/previous/results)VODMovieDetail— full provider-info shape (stream_id, plot, cast, backdrop, cover_big, container_extension, all the TMDB/IMDB IDs)VODEpisode— per-episode shape withepisode_number/season_number/air_date/duration_secsVODSeriesDetail— series header + episodes keyed by season-string ("1", "2", ...) per the server's JSON shapeVODProvider— multi-provider entry; custominit(from:)flattens the nestedm3u_account {id, name}sub-object into top-levelm3uAccountId/m3uAccountNamefor caller convenience; customencode(to:)round-trips the nested shapeNexusPVR/Core/Services/DispatcherClient.swift— 6 client methods:fetchVODCategories(categoryType:)→[VODCategory]fetchVODContent(page:pageSize:search:category:)→VODPage(unified browse + search + category filter)fetchMovieDetail(id:forceRefresh:)→VODMovieDetailfetchMovieProviders(id:)→[VODProvider]fetchSeriesDetail(id:includeEpisodes:)→VODSeriesDetailvodStreamURL(contentType:uuid:m3uAccountId:streamId:)→URL?(proxy URL for playback; URLSession follows the 301 to the session URL automatically)NexusPVRTests/DispatcharrVODTests.swift— 17 tests:m3u_account, tolerate missingm3u_account, round-trip with nestedm3u_accountAll tests pass snake_case wire-format pins so a future rename is caught at PR-review time, not by a confused user with a broken decode.
What's NOT in this PR (out of scope, follow-ups)
NexusPVR/Features/VOD/VODBrowseView.swift+VODBrowseViewModel.swift+VODItemCard.swift— paginated grid with search + category filter, debounce 0.3s, infinite-scroll pagination. Pure SwiftUI.NexusPVR/Features/VOD/VODMovieDetailView.swift— cover, metadata grid, play button (callsvodStreamURL(contentType:"movie", uuid:item.uuid)), optional YouTube trailer link.NexusPVR/Features/VOD/VODSeriesDetailView.swift+VODEpisodeRow.swift— season picker (segmented control on iOS / wheel on tvOS), per-season episode list.NexusPVR/Navigation/AppState.swift— add.vodcase to theTabenum under#if DISPATCHERPVR. Mirror change inNavigation/AppState.swiftfor the existing platform-specific routing.NexusPVR/Navigation/NavigationRouter.swift— add the VOD tab to iOS / tvOS / macOS routing variants.Issue #17 is split into 2 PRs along these lines so the data layer + client methods are reviewable and Linux-validatable independently, then the SwiftUI plumbing lands in focused follow-ups.
Test plan
swift test --parallelpasses 164/164, including the 17 newDispatcharrVODTests. The harness covers every model's decode / round-trip / missing-fields paths; it does not exercise the URL builder (which needs URLSession + a live Dispatcharr).xcodebuild test -project NexusPVR.xcodeproj -scheme Dispatcharr— synchronized-folder test target picks upDispatcharrVODTests.swiftautomatically (nopbxprojedit needed). Existing tests pass because the new client methods are additive — no existing call site changed.fetchVODContent/fetchMovieDetail/fetchSeriesDetail/vodStreamURLagainst a real backend; this PR's tests guarantee the wire format matches.Migration safety
pbxprojedits, no existing test broke./api/vod/introduction will get a thrownPVRClientError.invalidResponse(404 surfaced) from any new client method, which the UI caller is expected to render as "feature not available on this server version" — the same pattern the existinggetM3UAccounts()andgetChannelStreams()use for newer endpoints.Author note
drvolks <drvolks@users.noreply.github.com>— global git config. Same as PRs #129 and #130. PR #128's first commit is the only one with the wrong author identity (hermes <hermes@local>); amending that is a singlegit commit --amend --reset-author && git push --force-with-leaseon the feat-107 branch and the maintainer can ask for it.