# Aesthetic Computer — Apple TV (tvOS) App Plan ## 0. Product Intent Build a **tvOS Apple TV app** that functions as a **passive, cinematic playback surface** for Aesthetic Computer artifacts—primarily **pre-rendered MP4 "Tapes"** generated by Oven. **Core rule:** **Oven renders. Apple TV plays.** ### Explicit Non-Goals - No WebViews / no WebKit - No KidLisp editor or generative rendering - No chat, wallets, minting, or comments - No dense menus or text input - No feature parity with the website --- ## 1. Primary User Experience ### Tape Mode (default) - App launch → immediate fullscreen playback - Sequential playback of tapes - Auto-advance on end - Optional loop or shuffle **Siri Remote mapping** - ▶︎ Play / Pause - ← Previous tape - → Next tape - ↑ Show overlay (metadata) - ↓ Hide overlay - Long-press ▶︎ (optional): Favorite / Unfavorite ### Minimal Overlay (auto-hide) Shown only when requested. ``` Aesthetic Computer Tape 014 · 3:44 · 1280×720 @ 30fps Seed: 839201 · Generator: kidlisp-oven ``` --- ## 2. Content Model ### Tape (canonical artifact) A **Tape** is a finished audiovisual unit. **Required** - `id` - MP4 URL (H.264 + AAC) - Duration (seconds) **Recommended** - Title - Creation timestamp - Resolution / FPS - Seed - Generator name + version - Tags (tape, gallery, ambient, etc.) ### Tape JSON Schema (draft) ```json { "id": "tape-014", "title": "Tape 014", "mp4": "https://aesthetic.computer/tapes/014.mp4", "duration": 224, "resolution": "1280x720", "fps": 30, "seed": 839201, "generator": "kidlisp-oven", "created_at": "2025-01-03T10:42:00Z", "tags": ["tape", "glitch"] } ``` ### Feed Format **v1** - `GET /tv/tapes.json` ```json { "tapes": [Tape] } ``` **v2 (future)** - Multiple feeds: tapes, ambient, highlights, live --- ## 3. Oven Responsibilities (Upstream) ### Rendering - Deterministic, seeded runs - Headless Chromium / WebGL / KidLisp - Fixed resolution - Fixed framerate - Fixed duration ### Encoding (Apple-safe, frozen) - Video: H.264 - Pixel format: yuv420p - Audio: AAC LC - Constant framerate - No alpha, no exotic color spaces ### Outputs - MP4 files - Optional HLS (live / long-form) - JSON feed index ### Hosting - HTTPS only - CDN-friendly - Permissive CORS for JSON + MP4 - Long cache headers for MP4s --- ## 4. Apple TV App Responsibilities ### Must Do - Fetch JSON feeds - Parse into models - Cache feed locally - Fullscreen playback via AVPlayer - Auto-advance and retry logic - Minimal overlays ### Must Not Do - No WebViews - No private APIs - No rendering or heavy computation - No complex UI hierarchy --- ## 5. App Modes (Phased) ### Phase 1 - Tape Mode only ### Phase 2 - Gallery Mode (slower pacing) - Local favorites ### Phase 3 - Ambient Mode (screensaver-grade loops) ### Phase 4 - Live Mode (HLS + fallback to archive) --- ## 6. Architecture Overview ### Tech Stack - SwiftUI (tvOS) - AVKit / AVPlayer - URLSession + Codable - On-disk caching (Application Support) ### Module Sketch ``` AppleTVApp/ ├─ App/ │ ├─ AestheticTVApp.swift │ └─ AppState.swift ├─ Models/ │ ├─ Tape.swift │ └─ Feed.swift ├─ Services/ │ ├─ FeedService.swift │ ├─ CacheService.swift │ └─ ReachabilityService.swift ├─ Player/ │ ├─ PlayerCoordinator.swift │ ├─ PlayerView.swift │ └─ OverlayView.swift └─ Utilities/ └─ Logger.swift ``` --- ## 7. Playback Logic - Maintain `tapes: [Tape]` - Track `currentIndex` - On playback end: - Advance sequentially or shuffle - Wrap if loop enabled - On error: - Skip tape - Continue playback - Overlay never blocks playback --- ## 8. Physical Apple TV Build & Test Plan ### Goal Validate real-device playback ASAP. ### Fastest MVP Test 1. Create tvOS SwiftUI app 2. Hardcode one Oven MP4 URL 3. Fullscreen AVPlayer playback 4. Pause / resume via remote 5. Run for 5–10 minutes ### Pairing Apple TV: - Settings → Remotes and Devices → Remote App and Devices Xcode: - Window → Devices and Simulators → Pair Apple TV ### Signing - Use Automatic Signing - Resolve provisioning before feed logic ### Definition of Done - App installs from Xcode - Smooth playback - Relaunch resumes correctly --- ## 9. Performance Constraints - Prefer 720p for reliability, 1080p optional - H.264 baseline / main profile - Stable framerate - App runs unattended for hours --- ## 10. Security & Compliance - Public HTTPS endpoints only - No WebKit - No private APIs - App Store–safe playback behavior --- ## 11. Phased Delivery ### Phase 1 — MVP Player - Single hardcoded MP4 - Play / pause - Real-device validation ### Phase 2 — Feed Playback - Fetch + cache JSON feed - Auto-advance playlist - Prev / next controls ### Phase 3 — Experience Polish - Gallery / Ambient modes - Better pacing + overlays ### Phase 4 — Live - HLS stream support - Archive fallback --- ## 12. Success Criteria - Launch → playback in under 1 second - Zero dropped frames - Stable for long sessions - Feels calm, intentional, gallery-grade --- ## Summary This app is **a television for Aesthetic Computer**, not a computer on a television. Oven produces finished tapes. Apple TV presents them beautifully. --- ## Integration Notes ### Existing Infrastructure #### MongoDB `tapes` Collection The codebase already has a `tapes` collection with this schema: ```javascript { _id: ObjectId, code: string, // 3-char short code (e.g., "xyz") slug: string, // piece-timestamp format user: string | null, // auth0 subject or null for guests when: Date, bucket: string, // "user-aesthetic-computer" or "art-aesthetic-computer" at: { // ATProto record (if synced) rkey: string, uri: string, cid: string }, nuked: boolean } ``` #### Existing API Endpoints **`GET /api/tv`** ([tv.mjs](../system/netlify/functions/tv.mjs)) - Already fetches tapes with ATProto video URLs - Supports `?types=tape` filter - Returns structured feed with video URLs from ATProto PDS - Response includes: `id`, `type`, `code`, `slug`, `owner`, `when`, `acUrl`, `media.videoUrl` **`GET /api/get-tape`** ([get-tape.mjs](../system/netlify/functions/get-tape.mjs)) - Query by `?code=xyz` or `?slug=...` - Returns: `slug`, `code`, `when`, `bucket`, `user`, `nuked` #### Video Hosting - **Primary**: ATProto PDS blobs at `https://at.aesthetic.computer/xrpc/com.atproto.sync.getBlob` - **Source ZIPs**: DigitalOcean Spaces (`art-aesthetic-computer` bucket) - **Oven Server**: `oven.aesthetic.computer` processes ZIPs → MP4s ### Recommended New Endpoint: `/api/tv/tapes.json` Create a dedicated Apple TV feed endpoint: ```javascript // system/netlify/functions/tv-tapes.mjs // GET /api/tv/tapes - Returns tape feed for Apple TV export async function handler(event) { const database = await connect(); const tapes = await fetchTapes(database.db, { limit: 100 }); // Transform to Apple TV schema const feed = tapes.map(t => ({ id: t.code, title: `Tape ${t.code.toUpperCase()}`, mp4: t.media.videoUrl, duration: null, // Would need to store in MongoDB resolution: "1280x720", fps: 30, seed: null, generator: "kidlisp-oven", created_at: t.when, tags: ["tape"] })); return respond(200, { tapes: feed, total: feed.length, lastUpdated: new Date().toISOString() }); } ``` ### Integration Recommendations 1. **Extend `tapes` MongoDB schema** to include: - `duration` (seconds) - from metadata.json - `resolution` - from metadata.json - `fps` - from metadata.json - `seed` - if available from piece 2. **Update Oven** to store this metadata when processing 3. **Create `/api/tv/tapes.json`** endpoint that: - Returns Apple TV-friendly JSON - Filters to only tapes with valid ATProto video blobs - Includes CORS headers for tvOS fetch - Caches response (5-10 minute TTL) 4. **Video URL Pattern**: ``` https://at.aesthetic.computer/xrpc/com.atproto.sync.getBlob?did={did}&cid={videoCid} ``` These are direct H.264 MP4 URLs suitable for AVPlayer. ### Data Flow ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Recording │────▶│ Oven │────▶│ ATProto │ │ (ZIP) │ │ (H.264) │ │ (Blob) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ ▼ │ ┌─────────────┐ │ │ MongoDB │◀───────────┘ │ (tapes) │ └─────────────┘ │ ▼ ┌─────────────┐ │/api/tv/tapes│ │ .json │ └─────────────┘ │ ▼ ┌─────────────┐ │ Apple TV │ │ App │ └─────────────┘ ``` ### Phase 1 MVP Implementation Path 1. **Week 1**: Create tvOS app scaffold with hardcoded test MP4 URL 2. **Week 2**: Implement `/api/tv/tapes.json` endpoint using existing `fetchTapes()` logic 3. **Week 3**: Connect app to endpoint, implement playlist navigation 4. **Week 4**: Polish overlay UI, test on physical device