Trey t 346557af88 Fix hero to match reference: white surface, image right, outlined CTA
Completely rebuilt FeaturedGameCard to match the Dribbble vtv reference.
White/cream background surface instead of dark card. Stadium image sits
on the right side and fades into white via left-to-right gradient. Dark
text on light background. "Watch Now" as outlined orange pill (not
filled). Plus icon for add-to-multiview.

Title uses split weight: thin "Houston Astros vs" + bold orange "Seattle
Mariners" — mimicking the "modern family" typography split.

Cleaned up DashboardView header: removed bulky MLB/date/stat pills
section. Replaced with compact inline date nav: chevron left, date text,
chevron right, "Today" link, game count + live count on the right. One
line instead of a full section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:17:20 -05:00
2026-03-26 15:37:31 -05:00

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 ofapp 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.

Description
No description provided
Readme 473 KiB
Languages
Swift 100%