# mlbTVOS Personal MLB streaming app for Apple TV and iPhone/iPad, built with SwiftUI. Streams live and archived MLB games, plus a few non-MLB channels (MLB Network, an authenticated private feed). ## Targets - `mlbTVOS` — tvOS application (primary) - `mlbIOS` — iOS/iPadOS application (shares most sources with the tvOS target) - `mlbTVOSTests` — Swift Testing unit tests The Xcode project is generated from `project.yml` via [XcodeGen](https://github.com/yonaskolb/XcodeGen). After editing `project.yml` or adding/removing source files, run: ```sh xcodegen generate ``` ## Building Requires Xcode 26.3+ and tvOS/iOS 18.0 SDKs. ```sh # tvOS xcodebuild build -project mlbTVOS.xcodeproj -scheme mlbTVOS \ -destination 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation)' # Tests xcodebuild test -project mlbTVOS.xcodeproj -scheme mlbTVOSTests \ -destination 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation)' ``` ## Features ### Dashboard Today's live/scheduled/final games, standings ticker, and special channel entry points. Tapping a game opens the Game Center with at-bat timeline, pitch sequence, spray chart, and strike zone overlays. ### Multi-stream view Up to four simultaneous streams in an adaptive grid. Only one stream holds audio focus at a time; tap any tile to transfer focus. Streams can be single broadcasts, team-filtered feeds, or special channels. ### Single-stream player Full-screen AVPlayer with HLS adaptive streaming, custom HTTP headers, and automatic retry on failure. ### Special playback channels - **MLB Network** — 24/7 channel via the public MLB stream - **Werkout NSFW** — authenticated private feed served by [`ofapp`](https://gitea.treytartt.com) on the home server; uses a JWT cookie and rotates clips through a per-model random shuffle ### Video shuffle (per-model random) `VideoShuffle` groups clips by source folder (e.g. which model/creator the video came from) and picks randomly one bucket at a time, excluding the previously-played bucket. This guarantees **no back-to-back** same source and produces a roughly uniform distribution across sources even when one source dominates the raw data by a large factor. See `mlbTVOS/Services/VideoShuffle.swift` and `mlbTVOSTests/VideoShuffleTests.swift` for the pure functions and their test coverage. ## Architecture - **`mlbTVOS/mlbTVOSApp.swift`** — app entry point, creates a single `GamesViewModel` injected into the environment - **`mlbTVOS/ViewModels/GamesViewModel.swift`** — observable state store for games, standings, active streams, audio focus, and video shuffle bags; fetches from MLB Stats API and the auxiliary stream service - **`mlbTVOS/ViewModels/GameCenterViewModel.swift`** — per-game live data (at-bats, pitches, box score) used by the Game Center screen - **`mlbTVOS/Services/`** — API clients (`MLBStatsAPI`, `MLBServerAPI`) and pure helpers (`VideoShuffle`) - **`mlbTVOS/Views/`** — SwiftUI screens and component library (`Components/AtBatTimelineView`, `PitchSequenceView`, `SprayChartView`, `StrikeZoneView`, etc.) All new Swift code uses Swift 6 strict concurrency (`SWIFT_STRICT_CONCURRENCY: complete`). ## Configuration No secret management is set up. The authenticated feed cookie is hardcoded in `mlbTVOS/Views/DashboardView.swift` (`SpecialPlaybackChannelConfig.werkoutNSFWCookie`) and must be refreshed manually when the JWT expires — generate a new one from the `ofapp` server's `jwt_secret` using the same `userId` and a longer `exp`.