A free, open-source video player and personal watch-tracker for Apple platforms.
Play your own movies and shows, enrich them with TMDB, and keep your watch history in your own iCloud — never on someone else's server.
Edendale is a SwiftUI player that aims to be easy to use, maximally performant, and double as a centralised place to manage what you've watched and what you think of it — with all personal data stored in your own iCloud.
- Play local video — open a file directly, or import whole folders of pre-downloaded movies and shows. Playback runs on VLC (via SwiftVLC), so a wide range of formats work: mp4, mkv, avi, webm, ts, mpg, and many more (see
LibraryController.supportedExtensions). - Build a library from your files — imported folders are scanned and each filename is classified as a movie (
Title (2020).mkv) or a TV episode (Show.S01E02.mkv,Show.1x02.mkv) before any network call. The index is persisted immediately; metadata enrichment happens in the background. - Enrich with TMDB — posters, overviews, ratings, and episode details are fetched from TMDB and shown alongside your files. You can also browse TMDB for movie/show information independently of local files.
- Track watch progress in your iCloud — which movies and shows you've watched, and how far into them you are, syncs across your devices via CloudKit. Nothing leaves your device except TMDB requests (for metadata) and your own private iCloud.
- Planned — centralised review/critics management: your own ratings and reviews, stored the same privacy-first way.
Active development. The data layer, TMDB service, filename parser, library import, and iCloud watch-progress sync are in place, and the app now has a full UI shell:
- Platform-adaptive
TabView(RootView) — sidebar on iPad/macOS, tab bar on iOS, ornament on visionOS, top tab bar on tvOS — with Movies & Shows, Downloaded, Search, and Settings. - A dark "cinematic" theme built entirely from asset-catalog color sets, plus custom fonts (Bebas Neue / Inter) registered at runtime.
- Movies & Shows — TMDB browse shelves (trending / popular / top-rated / genre collections) with a Continue-Watching hero.
- Downloaded — the local library: folder import, per-folder rescan/remove, movie & show listings with watch state.
- A shared media detail view (TMDB and local items) and VLC player presentation: in-place full-screen playback on iOS, visionOS, and tvOS, with a dedicated player window on macOS.
tvOS runs the same shared UI with platform-specific treatment (card-style poster focus, captions overlaid on artwork, trailers and local folder import compiled out — Apple TV has no user-browsable local storage; network sources are the planned path).
Still in progress: the player watch-progress loop (resume/seek + periodic writes), TMDB-only season/episode browsing, the Search tab, a Settings entry point on tvOS, and manual UI verification passes. See TASKS.md for the detailed phase tracker.
Edendale/ App target — thin per-target entry points only
EdendaleApp.swift @main; injects stores, registers fonts
ContentView.swift Renders RootView
VideoPlayer.swift SwiftUI wrapper around SwiftVLC's Player/VideoView
Info.plist Exposes the TMDB key as "TMDBAPIKey"
Edendale.entitlements CloudKit container + app group
Shared/ Code shared across all targets
AppIdentifiers.swift Single source of truth for bundle / iCloud / app-group ids
Persistence.swift TWO stores — see "Persistence" below
Models/
VideoFolder.swift An imported folder (path + security-scoped bookmark)
Movie.swift Local file + optional TMDB metadata (SwiftData)
TVShow.swift, Episode.swift
WatchProgress.swift Value type for watch state
CDWatchProgress.swift CoreData entity backing iCloud sync
MediaRating.swift Rating value type (TMDB score/votes)
Controllers/
LibraryController.swift Folder import, file scanning, classify → persist → enrich
MediaParser.swift Filename → movie(title, year) or episode(show, S, E)
TMDBService.swift Thin async TMDB v3 API client (search + episode detail)
TMDBBrowse.swift Browse endpoints (trending/popular/discover) + unified DTOs
TMDBRatingsProvider.swift / RatingsEnvironment.swift Ratings source
TMDBAccountStore.swift TMDB account sign-in (v4 auth flow), token in synced keychain
KeychainStore.swift iCloud-synchronized keychain wrapper (Security.framework)
WatchProgressStore.swift Watch state on CoreData + CloudKit, @Observable cache
Views/ All SwiftUI UI (shared across platforms)
RootView.swift Adaptive TabView (Movies & Shows / Downloaded / Search / Settings)
Theme/ Theme.swift (semantic colors + radii), Typography.swift (+ FontRegistrar)
Components/ Reusable views (PosterCard, MediaShelf, GlassCard, ScoreTile, …)
MoviesShows/, Downloaded/, Detail/, Player/, Search/, Settings/ Per-area screens
Fonts/ Bebas Neue + Inter (registered at runtime)
Assets.xcassets/ Colors/, Icons/ (Font Awesome symbol sets), Images/
EdendaleTests/, EdendaleUITests/
- SwiftData, local-only (
Persistence.sharedModelContainer): the media library (VideoFolder,Movie,TVShow,Episode). It references device-specific file paths and security-scoped bookmarks, so CloudKit mirroring is explicitly disabled (cloudKitDatabase: .none). Don't re-enable it — the models would also violate CloudKit's "all attributes optional" rule. - CoreData + CloudKit (
Persistence.cloudPersistentContainer): watch progress only, keyed by TMDB id + media type, synced through the private iCloud containeriCloud.com.BaBaSaMa.Edendale.WatchProgressStorekeeps an in-memory@Observablecache and merges remote changes fromNSPersistentStoreRemoteChangenotifications.
Identifiers (bundle, iCloud container, app group) have a single source of truth
in Shared/AppIdentifiers.swift, which must match Edendale.entitlements. Use
AppIdentifiers.defaults (the app-group UserDefaults suite) rather than
UserDefaults.standard.
Requirements: Xcode with the iOS / macOS / visionOS / tvOS SDKs, and an Apple developer account signed in if you want CloudKit sync to work on device.
- Clone the repo and open
Edendale.xcodeproj. - Create your secrets file from the template:
cp Shared/Example.xcconfig Shared/Secrets.xcconfig
- Sign up for free TMDB API access at https://www.themoviedb.org/settings/api, copy the API Read Access Token (the long
eyJ…token), and paste it afterTMDB_READ_ACCESS_TOKEN =inShared/Secrets.xcconfig. (The short v3 "API Key" still works as a legacy fallback viaTMDB_API_KEY =.) - Build & run.
Secrets.xcconfig is gitignored — the token flows Secrets.xcconfig → Info.plist (TMDBReadAccessToken) → read from the bundle at runtime and sent as an Authorization: Bearerheader (seeTMDBService`). Builds work without a token;
TMDB enrichment just returns nothing. Never commit a real token or key.
| Package | Purpose |
|---|---|
| SwiftVLC | Video playback (libVLC wrapper) |
| Kingfisher | Poster/backdrop image loading & caching |
| Alamofire | Networking |
| Drops | Lightweight in-app alerts |
- Free and open source — no accounts, no subscriptions, no telemetry.
- Your data stays yours — watch history and reviews live in your personal iCloud.
- Performance first — persist fast, enrich in the background, cache aggressively.
This product uses the TMDB API but is not endorsed or certified by TMDB.