Hero card was 470px tall with 36px padding — content was clipping off the left edge and bottom. Increased to 560px tall with 48px horizontal padding. Added minimumScaleFactor(0.7) to title text so long matchup names shrink instead of clip. Strengthened the left gradient overlay (0.92→0.75→0.4→0.15) so text is readable over the pitcher action photo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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. After editing
project.yml or adding/removing source files, run:
xcodegen generate
Building
Requires Xcode 26.3+ and tvOS/iOS 18.0 SDKs.
# 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
ofappon 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 singleGamesViewModelinjected into the environmentmlbTVOS/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 servicemlbTVOS/ViewModels/GameCenterViewModel.swift— per-game live data (at-bats, pitches, box score) used by the Game Center screenmlbTVOS/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.