DumpertTV

June 14, 2026 · View on GitHub

DumpertTV - unofficial Dumpert client for Apple TV (tvOS)

DumpertTV

TestFlight Platform Swift 6.0 Xcode 26.3 License Stars Last Commit Repo Size XcodeGen

DumpertTV is an unofficial Dumpert app for Apple TV — a native tvOS client built with Swift 6 and SwiftUI that lets you browse, search and stream Dumpert videos on the big screen. Watch Toppers, Nieuw and the Classics, with Top Shelf integration, CloudKit sync across your Apple TVs, and SharePlay for watching together. Free public TestFlight beta below.

Disclaimer

This project is not affiliated with, endorsed by, or associated with Dumpert or DPG Media B.V. Dumpert is a registered trademark of DPG Media B.V. All trademarks belong to their respective owners. This app consumes the public Dumpert API. Use at your own risk.


Installation via TestFlight

TestFlight

Join TestFlight Beta

The easiest way to install DumpertTV on your Apple TV:

  1. Install TestFlight from the App Store on your Apple TV and iPhone
  2. Open the TestFlight invite link on your iPhone and accept the invite
  3. Open TestFlight on your Apple TV and install the app

Features

Content Browsing

  • 6 top-level tabs (Dutch UI labels): Toppers (Top), Nieuw (New), Categorieën (Categories), Gekeken (Watched), Zoeken (Search), Instellingen (Settings)
  • The Categorieën tab consolidates Reeten, VrijMiCo, Dashcam, Classics and DumpertTV behind an in-view pill filter (selection persists via @SceneStorage)
  • Hero banner with horizontally scrolling carousel and face-centered thumbnails
  • Infinite scroll pagination on category and classics views
  • Skeleton loading with shimmer animation while content loads
  • Top Shelf extension showing trending content directly on the Apple TV home screen (honors the NSFW setting)
  • Immersive background with dynamic blurred imagery
  • Loading screen with logo animation and a random sound effect (NSFW sounds are withheld when NSFW content is hidden)
  • Sort order support for category tabs and search results
  • Context menu on video cards (long press)

Video Player

  • Full-screen video playback via AVPlayerViewController
  • Autoplay with configurable up-next overlay and countdown timer
  • Next video preloading for seamless playback
  • Playback speed control (0.5x, 0.75x, 1x, 1.25x, 1.5x, 2x)
  • Watch progress tracking with throttled saves (5-second intervals)
  • Resume overlay when returning to a previously watched video
  • Top comment overlay showing popular comments during playback
  • Now Playing info on the Lock Screen and Control Center
  • Swipe gestures on the Siri Remote to skip to previous/next video
  • Watched badge indicator on already-viewed content

Watched Items

  • Gekeken (Watched) sub-tab under Categories, showing previously watched videos
  • Track and manage watch history

SharePlay

  • Watch Together via SharePlay (GroupActivities)
  • Synchronized playback across multiple Apple TVs
  • Participant count indicator

Photo Viewer

  • Full-screen photo display with zoom controls
  • Overlay with metadata (title, date, kudos)
  • Full-text search with the Dumpert API
  • Filters: media type, time period, minimum kudos, duration
  • Sort order: relevance, date, kudos
  • Popular tags and recent search suggestions
  • In-memory result caching (5-minute TTL)
  • Search history persistence

Sync & Offline

  • CloudKit sync for watch progress, settings, curation entries, and search history across Apple TV devices
  • Delta sync with change tokens for efficient updates
  • Offline support with network monitoring banner
  • ETag-based HTTP caching (304 Not Modified) for API responses
  • Retry logic with exponential backoff (3 attempts, 2^n second delays) on 5xx and network errors

Localization

  • Dutch (nl) and English (en) via String Catalogs
  • All user-facing strings use String(localized:comment:) for translator context

Accessibility

  • VoiceOver labels throughout all views
  • Adjustable action on hero carousel for screen reader users

Deep Linking

  • URL scheme: dumpert://video/{id}
  • Used by the Top Shelf extension to open videos directly

Screenshots

ToppersNieuw
Toppers — trending Dumpert videos on Apple TVNieuw — latest Dumpert videos
CategorieënDumpertTV
Categorieën tab with pill filter (Dashcam) on Apple TVDumpertTV section
ZoekenInstellingen
Zoeken — full-text Dumpert search with filtersInstellingen — app settings

Design System

The app's visual language — brand, design tokens (color, typography, spacing, radius, motion), components and patterns — is documented in a single, self-contained page derived directly from the SwiftUI source, so design and code stay in sync.

It covers the dumpiGreen brand palette, the dark-only tvOS surface system (Liquid Glass with pre-26 fallbacks), SF Symbols iconography, and focus/motion rules — plus live reproductions of every reusable component (video card, kudos/watched badges, filter chips, toasts, skeletons, settings rows, hero and tab bar) with their anatomy, states and accessibility requirements.


Requirements

RequirementVersion
Xcode26.3+
tvOS deployment target18.0+
Swift6.0 (strict concurrency)
XcodeGenLatest
Apple Developer accountRequired for CloudKit and code signing

Getting Started

1. Install XcodeGen

brew install xcodegen

2. Clone the repository

git clone https://github.com/rm335/dumpert-apple-tv.git
cd dumpert-apple-tv

3. Generate the Xcode project

xcodegen generate

The .xcodeproj is generated from project.yml — never edit it directly.

4. Open in Xcode

open Dumpert.xcodeproj

5. Configure signing

  • Select your development team for both the Dumpert and DumpertTopShelf targets.
  • Change the bundle identifiers if needed (default: nl.dumpert.tvos).

6. Configure CloudKit (optional)

CloudKit sync is optional. If you want cross-device sync:

  1. Update Dumpert/Dumpert.entitlements with your own iCloud container identifier.
  2. Update DumpertTopShelf/DumpertTopShelf.entitlements with your own app group.
  3. Create the corresponding CloudKit container in the Apple Developer portal.

Without CloudKit, the app works fully with local-only persistence.

7. Build and run

Build and run on an Apple TV or the tvOS Simulator.


Architecture

Overview

┌──────────────────────────────────────────────────────────────┐
│                        DumpertApp                            │
│                   (SwiftUI @main entry)                      │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  LoadingScreenView → ContentView (TabView, 5 tabs)     │  │
│  │  Toppers │ Nieuw │ Categorieën │ Zoeken │ Instellingen │  │
│  └──────────────────────┬─────────────────────────────────┘  │
│                         │                                    │
│                 @Environment                                 │
│                         │                                    │
│  ┌──────────────────────▼─────────────────────────────────┐  │
│  │            VideoRepository                              │  │
│  │      @Observable @MainActor                             │  │
│  │      Single source of truth                             │  │
│  └───┬──────────┬──────────┬──────────┬───────────────────┘  │
│      │          │          │          │                       │
│  ┌───▼───┐ ┌───▼────┐ ┌───▼───────┐ ┌▼──────────────┐       │
│  │ API   │ │ Cache  │ │ CloudKit  │ │ NowPlaying /  │       │
│  │ Client│ │ Service│ │ Service   │ │ SharePlay     │       │
│  │(actor)│ │(actor) │ │ (actor)   │ │ (@Observable) │       │
│  └───────┘ └────────┘ └───────────┘ └───────────────┘       │
└──────────────────────────────────────────────────────────────┘

Key Patterns

PatternUsage
@Observable + @MainActorVideoRepository, NetworkMonitor, UserSettings, SharePlayService, ImmersiveBackgroundState — reactive state on main thread
Actor isolationDumpertAPIClient, CacheService, CloudKitService, ImageCacheService — thread-safe services
Environment injectionVideoRepository, NetworkMonitor, ImmersiveBackgroundState, LoadingSoundPlayer injected via .environment()
Protocol-based DIAPIClientProtocol, CacheServiceProtocol for testability
Swift 6 strict concurrencySWIFT_STRICT_CONCURRENCY: complete across all targets

Data Flow

Dumpert API → DumpertItem (API model) → MediaItem (domain enum) → Video / Photo

                                         VideoRepository

                                    SwiftUI views via @Environment

Project Structure

dumpert/
├── project.yml                     # XcodeGen project configuration
├── Dumpert/                        # Main app target
│   ├── App/
│   │   ├── DumpertApp.swift        # @main entry, environment setup, deep linking
│   │   └── ContentView.swift       # Root TabView with 5 tabs + offline banner
│   ├── Models/
│   │   ├── API/                    # Codable API response models
│   │   │   ├── DumpertAPIResponse.swift
│   │   │   ├── DumpertItem.swift
│   │   │   ├── DumpertMedia.swift
│   │   │   ├── DumpertStats.swift
│   │   │   └── DumpertComment.swift
│   │   └── Domain/                 # App domain models
│   │       ├── Video.swift
│   │       ├── Photo.swift
│   │       ├── MediaItem.swift     # enum: .video(Video) | .photo(Photo)
│   │       ├── VideoCategory.swift
│   │       ├── UserSettings.swift
│   │       ├── WatchProgress.swift
│   │       ├── SearchFilter.swift
│   │       ├── SortOrder.swift
│   │       ├── CurationEntry.swift
│   │       ├── SearchHistoryEntry.swift
│   │       └── WatchTogetherActivity.swift  # GroupActivities for SharePlay
│   ├── Networking/
│   │   ├── DumpertAPIClient.swift  # Actor with ETag + retry
│   │   ├── APIClientProtocol.swift # Protocol for mocking
│   │   ├── APIEndpoint.swift       # URL routing
│   │   └── APIError.swift          # Error types
│   ├── Services/
│   │   ├── VideoRepository.swift   # @Observable source of truth
│   │   ├── CacheService.swift      # Disk cache (50MB LRU)
│   │   ├── CacheServiceProtocol.swift
│   │   ├── CloudKitService.swift   # iCloud delta sync
│   │   ├── CategoryService.swift   # Category filtering
│   │   ├── ImageCacheService.swift # Two-layer image cache (80MB mem + 200MB disk)
│   │   ├── ImagePrefetchService.swift
│   │   ├── NetworkMonitor.swift    # NWPathMonitor connectivity
│   │   ├── FaceDetectionService.swift
│   │   ├── RefreshScheduler.swift
│   │   ├── SharePlayService.swift  # GroupActivities coordination
│   │   ├── NowPlayingService.swift # MPNowPlayingInfoCenter + remote commands
│   │   ├── LoadingSoundPlayer.swift  # Random startup sound (NSFW-filtered)
│   │   ├── LoadingSoundCatalog.swift # Sounds.json manifest → NSFW classification
│   │   ├── ImmersiveBackgroundState.swift
│   │   ├── ThumbnailUpgradeService.swift
│   │   └── ThumbnailUpgradeDiskCache.swift
│   ├── ViewModels/
│   │   ├── VideoPlayerViewModel.swift
│   │   └── SearchViewModel.swift
│   ├── Views/
│   │   ├── Components/             # Reusable UI components
│   │   │   ├── VideoCardView.swift
│   │   │   ├── VideoPreviewView.swift
│   │   │   ├── VideoContextMenu.swift
│   │   │   ├── FaceCenteredThumbnailView.swift
│   │   │   ├── FocusableCapsuleButtonStyle.swift
│   │   │   ├── ImmersiveBackgroundView.swift
│   │   │   ├── SectionTitleView.swift
│   │   │   ├── KudosBadgeView.swift
│   │   │   ├── WatchedBadgeView.swift
│   │   │   ├── EmptyStateView.swift
│   │   │   ├── SkeletonView.swift
│   │   │   ├── ToastView.swift
│   │   │   └── AutoDismissModifier.swift
│   │   ├── LoadingScreen/
│   │   │   └── LoadingScreenView.swift  # Netflix-style loading with logo animation
│   │   ├── Player/
│   │   │   ├── VideoPlayerView.swift
│   │   │   ├── UpNextOverlayView.swift
│   │   │   ├── ResumeOverlayView.swift
│   │   │   ├── TopCommentOverlayView.swift
│   │   │   ├── NowPlayingOverlayView.swift
│   │   │   ├── SharePlayIndicatorView.swift
│   │   │   ├── FullScreenImageView.swift
│   │   │   ├── FullScreenImageOverlay.swift
│   │   │   └── ZoomControlsView.swift
│   │   ├── Search/
│   │   │   ├── SearchView.swift
│   │   │   ├── SearchSuggestionsView.swift
│   │   │   └── SearchFilterBar.swift
│   │   ├── Sections/
│   │   │   ├── ToppersSectionView.swift
│   │   │   ├── CategoriesSectionView.swift  # Pill-bar container for Reeten/VrijMiCo/Dashcam/Classics/Gekeken
│   │   │   ├── CategorySectionView.swift
│   │   │   ├── ClassicsSectionView.swift
│   │   │   └── WatchedSectionView.swift
│   │   └── Settings/
│   │       ├── SettingsView.swift
│   │       ├── SettingsComponents.swift
│   │       ├── SettingsPickerDestination.swift
│   │       └── UpNextSettingsView.swift
│   ├── Extensions/
│   │   ├── String+HTML.swift       # HTML tag/entity stripping
│   │   ├── Color+Dumpert.swift     # Brand colors (#65B32E)
│   │   └── Date+Formatting.swift
│   ├── Utilities/
│   │   ├── AppLogger.swift         # os.Logger categories
│   │   ├── DurationFormatter.swift # MM:SS formatting
│   │   └── MediaItem+Present.swift
│   ├── Assets.xcassets/
│   ├── Dumpert.entitlements
│   └── Info.plist
├── DumpertTopShelf/                # Top Shelf extension
│   ├── ContentProvider.swift       # TVTopShelfContentProvider
│   ├── DumpertTopShelf.entitlements
│   └── Info.plist
├── Shared/                         # Shared between app + extension
│   ├── TopShelfItem.swift
│   ├── TopShelfDataStore.swift     # App Group UserDefaults
│   └── TopShelfFetcher.swift
├── DumpertTests/                   # Unit tests (122 tests, 15 suites)
│   ├── ModelTests.swift
│   ├── APIDecodingTests.swift
│   ├── DumpertDateTests.swift
│   ├── DayGroupingTests.swift
│   ├── DurationFormatterTests.swift
│   ├── SearchFilterTests.swift
│   ├── SearchViewModelTests.swift
│   ├── CategoryServiceTests.swift
│   ├── CacheServiceTests.swift
│   ├── CloudKitMergeTests.swift
│   ├── CloudKitSettingsSyncTests.swift
│   ├── UserSettingsPersistenceTests.swift
│   ├── LoadingSoundCatalogTests.swift
│   ├── ErrorCaseTests.swift
│   ├── AutoNextPlayTests.swift
│   └── Fixtures/                   # JSON test fixtures
│       ├── hotshiz.json
│       ├── latest.json
│       ├── search_reeten.json
│       └── foto_item.json
└── LICENSE

API

The app uses the public Dumpert mobile API.

EndpointDescription
GET /hotshizCurrently trending items
GET /top5/week/{date}Top items of the week
GET /top5/maand/{date}Top items of the month
GET /latest/{page}Latest items (paginated)
GET /search/{query}/{page}?order=Search results (paginated, optional sort order)
GET /info/{id}Single item details
GET /classics/{page}Classic items (paginated)
GET /related/{id}Related items for a given video

Base URL: https://post.dumpert.nl/api/v1.0


Targets

The project has 3 targets, defined in project.yml:

TargetTypeBundle IDDescription
DumperttvOS Applicationnl.dumpert.tvosMain app
DumpertTopShelfApp Extensionnl.dumpert.tvos.topshelfTop Shelf content provider
DumpertTestsUnit Test Bundlenl.dumpert.tvos.tests122 tests across 15 suites

Tests

122 tests across 15 suites, using Swift Testing framework:

SuiteTestsWhat it covers
ModelTests9WatchProgress, CurationEntry, UserSettings, VideoCategory, HTML stripping
APIDecodingTests18API response decoding, Video conversion, HLS preference, tags parsing
DumpertDateTests9ISO8601 parsing — fractional seconds, mixed formats, Europe/Amsterdam boundaries
DayGroupingTests5Grouping the Nieuw feed into Europe/Amsterdam day buckets
DurationFormatterTests10Time formatting (MM:SS, edge cases)
SearchFilterTests5Filter activation for media type, period, kudos, duration
SearchViewModelTests12Search state, debouncing, pagination, cancellation, history persistence
CategoryServiceTests7Category → endpoint routing, sort order, curation flags
CacheServiceTests6Persistence of watch progress, settings, curation, search history
CloudKitMergeTests4Remote/local merge logic, deletion handling, change-token persistence
CloudKitSettingsSyncTests5Settings sync without overwriting local values
UserSettingsPersistenceTests4Settings round-trip, defaults, migration
LoadingSoundCatalogTests7NSFW classification of startup sounds (safe-by-default allowlist) + shipped manifest
ErrorCaseTests5API error descriptions, network/decoding/HTTP error handling, 5xx retry
AutoNextPlayTests16Playlist navigation, autoplay state, skip/previous, up-next overlay

Running Tests

# Generate project and run tests
xcodegen generate && xcodebuild test \
  -scheme Dumpert \
  -destination 'platform=tvOS Simulator,name=Apple TV' \
  -resultBundlePath TestResults

Tech Stack

TechnologyUsage
Swift 6.0Strict concurrency (complete mode)
SwiftUIAll UI, tvOS-native
AVKitVideo playback via AVPlayerViewController
GroupActivitiesSharePlay / Watch Together
MediaPlayerNow Playing info + remote command handling
CloudKitCross-device sync (private database, custom zone)
Network.frameworkNWPathMonitor for connectivity
Vision.frameworkFace detection for thumbnail centering
os.logStructured logging (.cloudKit, .cache, .network)
String CatalogsLocalization (Dutch + English)
XcodeGenProject generation from project.yml
Swift TestingUnit test framework

Configuration

Settings (in-app)

The Settings tab allows users to configure:

Display & Content:

  • Minimum kudos filter (0–500+)
  • NSFW content toggle (also withholds NSFW startup sounds and Top Shelf items)
  • Negative kudos toggle
  • Hide watched content
  • Smart thumbnails (automatic thumbnail upgrade)
  • Tile size (small, normal, large)

Playback:

  • Autoplay on/off
  • Video preview on focus
  • Up-next overlay, countdown, and minimum video length
  • Top comment overlay mode (off, single, all) with reading speed
  • Swipe-to-skip on Siri Remote
  • Resume overlay
  • Minimum Reeten duration filter

Data & Storage:

  • Manual refresh
  • Clear cache, watch history, search history
  • Reset to defaults

Settings are persisted locally and synced via CloudKit.

Entitlements

EntitlementTargetPurpose
iCloud containersDumpertCloudKit sync
CloudKitDumpertiCloud database access
KV storeDumpertKey-value sync
App GroupsBothShare data between app and Top Shelf extension

Crash Reporting and Privacy

Official non-Debug builds can use Sentry for crash and error reporting. Sentry is disabled when no DSN is configured, so local contributor builds and forks do not report to the official project.

The integration does not send default PII, network breadcrumbs, failed request details, screenshots, view hierarchies, tracing, or profiling. Maintainers should review the complete Sentry setup and privacy checklist before enabling it for TestFlight or App Store builds.


Contributing

Contributions are welcome! Here's how:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Install XcodeGen: brew install xcodegen
  4. Generate the project: xcodegen generate
  5. Make your changes
  6. Run the tests to make sure everything passes
  7. Commit your changes with a clear message
  8. Push to your fork and open a Pull Request

Guidelines

  • Run xcodegen generate after changing project.yml
  • Never commit Dumpert.xcodeproj changes directly — edit project.yml instead
  • Maintain Swift 6 strict concurrency compliance
  • Add tests for new functionality
  • Use actors for new services, @Observable @MainActor for new state holders
  • Follow existing patterns for file organization
  • For UI work, follow the design system — reuse existing tokens and components rather than introducing new ones

License

This project is licensed under the MIT License — see the LICENSE file for details.


Acknowledgements

  • Dumpert for the public API
  • XcodeGen for declarative Xcode project management