ShowShark Client Features
ShowShark Client is a SwiftUI multiplatform app for iOS, iPadOS, macOS, tvOS, visionOS, and watchOS that connects to a ShowShark Server to browse, stream, and play media.
Platform Support
- iOS (iPhone): Full-featured with swipe gestures, pull-to-refresh, long-press menus, Picture-in-Picture, background audio, and lock screen controls
- iPadOS: Sidebar navigation with server list and media browser split view
- macOS: Native window with sidebar navigation, keyboard shortcuts, media key support, window frame persistence, and single-instance behavior
- tvOS: Focus-based interface with Siri Remote support, card-style layouts, dedicated server management view, top shelf images, and platform-appropriate app icons
- visionOS (Apple Vision Pro): Shares iPad navigation layout with native visionOS appearance (no custom backgrounds)
- watchOS (Apple Watch): Companion app with HLS video/audio playback, server list with Bonjour discovery, media browsing, channel and history sections, Digital Crown volume control, and buffering countdown UI
Server Connection
Discovery & Setup
- Automatic server discovery via Bonjour/mDNS
- Discovered servers show server name, preferred address (IPv4 listed first), and additional address count (e.g., "192.168.1.100 (+1 more)")
- Address picker when adding a discovered server with multiple IPv4/IPv6 addresses
- IPv6 link-local address support with zone identifiers
- Add, edit, and delete saved servers with JSON persistence
- Server form: display name, hostname/IP, port (default 18080), password
- iOS: swipe-to-delete and swipe-to-edit on saved servers
- macOS: swipe delete, context menu for edit/delete
- tvOS: card-based server management view with inline edit/delete buttons and Getting Started help section
- Hostname display sanitization: hides port suffix and IPv6 zone identifiers in the server list
Connection Lifecycle
- WebSocket over TLS secure connections
- Password-based authentication with device name and persistent device identifier
- Auto-reconnect with attempt tracking and exponential backoff retry intervals
- Fast-fail on initial connection when host is unreachable (5-second timeout instead of 30)
- Stale NWConnections properly cancelled to prevent leaked background retries
- Connection states: disconnected, connecting, connected, waiting, failed, cancelled, reconnecting
- Visual connection status indicator: colored dot (gray/green/red) or spinner for in-progress states
- Connection failure alert with actionable error messages
- Authentication failure alert
- Media browser stays visible during reconnection if user was previously authenticated
- Requests automatically wait up to 15 seconds for authentication during reconnection (prevents stale error messages on foreground resume)
- Authentication generation counter forces view refreshes after re-authentication
- Server update notification after login (dismissible, with "Ignore" option to suppress per-version)
Media Browsing
Directory Browser
- Grid-based media browser with dynamic column calculation (responsive to window/screen size)
- Three sections: Channels (subdivided by video/audio/photo), Library (directories and files), History
- Section headers from server-provided directory entries
- Pagination with infinite scroll for large directories and iCloud photo albums (100 items per page)
- Auto-refresh every 5 minutes while visible
- Pull-to-refresh on iOS
- Cmd+R keyboard shortcut and toolbar button for manual refresh on macOS/iPadOS
- Breadcrumb navigation (iOS/visionOS) with clickable path segments and home button
- Empty state screens with helpful instructions when no servers or content are available
- Fresh ScrollView instances per directory to prevent SwiftUI view reuse issues
Grid Items
- Folder entries: gradient background with SF Symbol icon (custom per-location), 16:9 aspect ratio
- File entries: thumbnail image, 16:9 aspect ratio
- Display name resolution: metadata title > display name > raw filename
- Custom location icons sent from server (SF Symbols)
Media Detail View
- File information: name, path, size, duration, media type
- Video track details: resolution, codec, bitrate, frame rate
- Audio track details: codec, channels (Mono/Stereo/5.1/7.1), bitrate, language
- Subtitle track details: language, format
- tvOS: list-based layout with large thumbnail
- Other platforms: scrollable layout with 600px max thumbnail
Video Playback
Player Controls
- Automatic video codec negotiation: detects H.265 (HEVC) hardware decode support at startup via
VTIsHardwareDecodeSupported, falls back to H.264 - Intelligent output resolution auto-selection: defaults to min(source resolution, device capability) per session — macOS uses native screen pixels, tvOS uses native screen pixels (4K on Apple TV 4K, 1080p on Apple TV HD), iOS/visionOS capped at 1080p; manual 720p/1080p/4K override available in picker
- Source video info displayed as "Input" row in resolution section (codec and resolution)
- Audio track and subtitle track selection before playback
- Play button in toolbar (iOS/macOS) or tvOS top button bar; when a resume position exists, shows confirmation dialog with "Play from beginning" and "Resume from X:XX" options
- Seek bar / scrubber with time display (not on tvOS)
- Auto-hiding controls with timer-based visibility
- In-app volume slider (0-100%) adjusting player volume without changing system volume, persisted across restarts (excluded on tvOS)
Adaptive Bitrate (ABR) Streaming
- Fully automatic bitrate adjustment — no user-facing bitrate or quality controls
- Client sends
PlaybackStatusReportevery 2 seconds with buffer-ahead level and sliding-window throughput estimate - Receives
BitrateChangeNotificationfrom server reflecting encoder bitrate changes - Throughput estimated from received video/audio frame byte counts via
ThroughputEstimator(thread-safe,NSLock-based, 5-second sliding window with EWMA smoothing) - Buffer-ahead calculated as last received PTS minus currently displayed PTS
- ABR reporting starts 3 seconds after playback begins to let startup boost fill the buffer
Resume & Position Tracking
- Auto-save playback position every 10 seconds
- Resume button: "Resume at H:MM:SS" appears when a saved position exists
- "Start Playback" resets to the beginning when a resume position is available
- Restarts from the beginning if saved position is within the last 60 seconds of duration
Chapter Navigation
- Chapter list display with expandable chapter picker
- Previous/next chapter buttons with 3-second threshold for previous chapter behavior
- Current chapter indicator
- Starting chapter selection before playback
Subtitle Settings
- User-adjustable subtitle timing delay from -3 to +3 seconds via subtitle settings sheet
- Subtitle font size selection (small, medium, large)
- Subtitle button inline with transport controls (visible during both single video and channel playback)
- tvOS: focus-aware subtitle button with grouped form styling
Playback Contexts
- Single video: standard file playback with File Details button in toolbar (iOS/macOS) or tvOS top button bar
- Channel video: channel program playback with schedule display and auto-advance to next program
Video Rendering
AVSampleBufferDisplayLayerfor zero-copy video rendering- Hardware-accelerated H.264 and H.265 (HEVC) decoding via VideoToolbox
- Display layer persists through pause/seek (shows last frame, no flush)
- Display layer persists through Picture-in-Picture transitions
End-of-Stream Handling
- Single video: dismisses the player
- Channel playback: auto-advances to next scheduled program
- Demo limit detection with user-facing message
Library Navigation from Playback
- "Go to Library" toolbar button (books.vertical icon) appears in VideoPlaybackView, AudioPlaybackView, and MusicChannelPlaybackView when the media has an associated library entry (movie, show, or album)
- Navigates directly to MovieDetailView, ShowDetailView, or AlbumDetailView
Platform-Specific Video Behavior
- macOS: Screen sleep prevention, window chrome hidden during playback, click-to-show controls
- tvOS: Inline icon-only button bar at top of pre-playback content for Play, Thumbnails, Library, and File Details (toolbar items do not render on tvOS); focus-based transport controls; Siri Remote play/pause and directional commands; focus/scroll position preserved on back-navigation
- iOS: Status bar hidden during playback via PreferenceKey propagation through NavigationStack, navigation bar back button hidden during playback
- iOS/macOS: Space bar keyboard shortcut for play/pause
Audio Playback
- Album art display loaded from server
- Repeat modes: Off, All (loop collection), One (loop current track)
- Shuffle: randomized playback order within collection
- Collection-based playback: albums, artists, and folders treated as collections
- Track navigation: previous/next track with shuffle awareness
- Auto-advance to next track on completion
- Scrubber / seek bar with elapsed and remaining time
- In-app volume slider (same as video, persisted)
- Space bar play/pause on macOS/iOS
- macOS: screen sleep prevention during audio playback
- macOS: card-based layout matching iOS design (rounded rect backgrounds, no list dividers)
- tvOS: three-zone layout (shuffle left, transport center, repeat right) with default focus on play/pause
- iOS/macOS: centered playback controls
Audio Engine
- AVAudioEngine with AVAudioPlayerNode
- PCM output, configurable sample rate, channels, and bits per sample
- Per-buffer peak detection with transparent attenuation (target peak 0.98) to prevent clipping distortion
- Buffer underrun detection and statistics
A/V Synchronization
- Audio-as-master-clock synchronization
- 50ms display tolerance, 100ms drop threshold
- Sync decisions: display (within tolerance), hold (video ahead), drop (video behind)
- Statistics tracking: dropped, displayed, and held frame counts
Lyrics
- On-demand synced lyrics during audio playback
- Lyrics persist across track changes: auto-fetches lyrics for the new song without dismissing the view
- Content-switch pattern: lyrics replace album art view with compact transport controls bar
- Song title and artist displayed in lyrics view navigation bar
- Time-synced line highlighting with auto-scroll following the current playback position
- tvOS: always auto-scroll
- iOS/macOS: manual scrolling pauses auto-scroll for 5 seconds before resuming
- Back action hierarchy: dismisses lyrics first, then dismisses playback view (per-platform: custom toolbar back button on iOS/visionOS, onExitCommand on tvOS, Escape key on macOS)
- Plain text lyrics fallback when synced lyrics unavailable
- Source attribution (e.g., "LRCLib")
- In-memory lyrics cache by file path
Now Playing & Media Controls
- MPNowPlayingInfoCenter integration: title, artist name, album title, album artwork, duration, elapsed time, playback rate
- Lock screen and Control Center display during music and video playback
- Lock screen scrubber for both audio and video via
changePlaybackPositionCommand - Remote command handling: play, pause, toggle play/pause, next track, previous track (collections), skip forward, skip backward (single file)
- Album artwork loaded via ThumbnailLoader for MPMediaItemArtwork display
- Audio-only session mode:
.defaultAVAudioSession mode for music (better for background playback),.moviePlaybackfor video - Available on iOS, macOS, and tvOS
- macOS media key support: hardware play/pause key controls ShowShark even when not frontmost, with debounce to prevent double-fire
Background Audio (iOS)
UIBackgroundModes: audiokeeps the app running while audio playsAVAudioSession.Category.playbackfor uninterrupted audio- WebSocket connection stays alive during background audio (async receive loop continues)
- ~30 second grace period after lock screen pause before potential iOS suspend
- Auto-reconnect handles recovery after suspend
Picture-in-Picture (iOS)
- Automatic PiP activation when app is backgrounded during video or channel playback
- Play/pause and skip controls via AVPictureInPictureController delegate
- UI restoration when returning from PiP (navigation views re-present playback modals)
- Deferred teardown when PiP is mid-transition
- Display layer shared between inline player and PiP
Channels
Channel Types
- Video channels: virtual TV with scheduled programming
- Music channels: continuous music with album art and track info
- Photo channels: synchronized full-screen photo slideshows
Channel Listing
- Grid display with live thumbnails and channel names
- Separate sections: Video Channels, Audio Channels, Photo Channels
- Channel thumbnail caching with 5-minute disk cache expiration
- Channels sorted alphabetically
Video Channel Playback
- Wraps the standard video player in a channel context
- Auto-advances to next program on stream completion
- Channel schedule display within the player
- Year appended to titles from metadata
Music Channel Playback
- Album art display with progress tracking
- "Up Next" program list showing upcoming schedule
- Auto-advance on playback completion
- Now Playing integration
Photo Channel Playback
- Full-screen slideshow with automatic advancement
- Photo metadata overlay: date taken and reverse-geocoded GPS location
- Tap to toggle controls visibility (chrome hidden by default)
- Screen sleep prevention on all platforms
- tvOS: truly full-screen with hidden navigation bar and edge-to-edge display
TV Guide (Channel Guide)
- 24-hour programming grid with horizontal scrolling
- Current time indicator (red line)
- Video channels only (music and photo channels excluded from guide)
- Program tiles with width proportional to duration
- Thumbnail backgrounds with dark gradient overlay for text readability
- Color-coded borders: blue for now-playing video, purple for music, gray for past/future
- Platform-specific time slot widths (tvOS: 800px, others: 500px)
Program Detail
- Thumbnail, title, channel name, time range, duration, media type
- Video programs: content rating badge, Rotten Tomatoes score, synopsis/overview
- Music programs: artist name, album title
- "Open Player" button for currently playing programs
- tvOS: pushed onto navigation stack instead of presented as sheet, with focus management
Library Search
Search
- Search bar with cancellation of previous in-flight searches
- Search fields bitmask: title, overview, genre, actor, year (all enabled by default)
- Result categories: movies, shows, genres, actors, artists, albums, songs, photo albums
- "Music Albums" section name (distinguished from Photo Albums)
- Library stats overview above search: aggregate counts for movies, shows, episodes, artists, albums, songs (responsive font sizing for compact-width devices)
- Empty state shows browsable genre sections (Movie Genres, TV Show Genres, Music Genres) as tappable capsule pills using FlowLayout, plus recent search history (capped at 10, deduplicated, most-recent-first, with Clear button)
Movie Detail
- Poster/backdrop image
- Title, year, MPAA rating badge, Rotten Tomatoes score
- Overview/synopsis
- Tappable genre pills (navigate to genre browse)
- Cast member cards with profile photos and character names (tappable, navigate to actor filmography)
- "Open Player" button
Show Detail
- Poster/backdrop image
- Title, TV content rating badge, overview
- Tappable genre pills
- Cast member cards with photos (tappable)
- Season selector for quick navigation between seasons
- Season/episode listing grouped by season, episodes tappable to episode detail
Episode Detail
- Title, season/episode label (S01E01 format), duration, overview
- "Open Player" button
- tvOS: auto-focus on play button
Genre Browse
- Genre-scoped browsing by media type: Movies, TV Shows, or Music
- Movies and Shows sections with 16:9 thumbnail grid cards
- Music genre browsing with Artists, Albums, and Songs sections
- Tappable entries navigate to movie, show, album, or artist detail
Actor Browse
- Actor profile photo header (120px, loaded via server proxy)
- Filmography: Movies and Shows sections
- Tappable entries navigate to movie or show detail
Album Detail
- Album art thumbnail
- Title, artist name, year, song count
- "Play All" button
- Track listing grouped by disc number (multi-disc support)
- Individual track tapping starts collection playback from that track
Artist Detail
- Artist name, album count, total track count
- "Play All" button (loads all album details to build complete artist collection)
- Discography section: albums with year and track count, tappable to album detail
Content Discovery
Discovery Modes
- Similar To: Search for a seed title in your library, then view top 10 most similar items ranked by multi-factor similarity scoring (keyword overlap, genre overlap, cast overlap, year proximity, rating proximity, optional embedding cosine similarity)
- By Feel: Filter library by time commitment (quick/medium/long/epic with media-type-aware thresholds) and mood (light & fun, intense, thought-provoking, heartwarming, dark & edgy); when filtered pool exceeds 10 candidates, initiates binary search narrowing with side-by-side candidate comparison cards
- By Genre: Browse top 20 candidates in a selected genre sorted by vote average
Discovery UI
- Wizard flow: media type selection, discovery mode, mode-specific sub-flow, binary search rounds (if applicable), final candidates
- BinarySearchView: side-by-side candidate cards with thumbnail, title, year, genre chips, overview snippet, and progress indicator
- RecommendationCandidatesView: scrollable card layout with navigation to movie/show detail
- "Find Similar" entry point on MovieDetailView and ShowDetailView
- "Discover" button in media browser toolbar
- Available on all platforms (iOS, iPad, macOS, tvOS, watchOS)
Photo Viewing
- Full-screen photo display with left/right navigation
- Full-size photo loading at screen resolution
- Adjacent photo prefetching (photos >2 indices away are released from memory)
- Photo metadata: EXIF date taken and GPS coordinates
- Reverse geocoding of GPS coordinates to "City, State, Country" with actor-based caching (coordinates rounded to 3 decimal places)
- Platform input: macOS arrow keys, tvOS directional commands, iOS/visionOS swipe gestures
- Info bar: filename, position counter (e.g., "3 of 15"), metadata line (date and location)
- iCloud Photo Library support via
__photos__virtual path prefix
Thumbnails & Caching
Thumbnail Cache
- Two-tier: NSCache (100MB memory limit) + disk storage (500MB limit)
- SHA256-based cache keys
- Sharded disk layout (2-character hex prefix subdirectories)
- 5-minute cache freshness check for channel thumbnails
- LRU eviction when disk cache exceeds size limit
Thumbnail Loading
- Request batching and deduplication
- Thumbnail sizes: grid (300x200), detail (800x600), person photo (200x300), full photo (screen resolution)
- Request serialization to prevent server hammering
- Default thumbnail offset: 300 seconds into the video
Batch Thumbnails (Scrub Grid)
- Per-minute thumbnail offsets computed from video duration
- Client-side cache pre-check: cached thumbnails populate immediately
- Uncached offsets requested from server in a single batch request
- Progressive loading: images appear as each server response arrives
- Tap any thumbnail to view full-resolution or jump to playback at that offset
Playback History
Position Tracking
- Server-backed playback position persistence via periodic and final offset updates
- Local
PlaybackPositionManageras compatibility fallback - Positions marked
@ObservationIgnoredto prevent unnecessary SwiftUI re-renders during auto-save
Play History
- Server-backed play history: movies, shows/episodes, and music tracked across all clients via
PlayHistoryService - Dynamic 16:9 thumbnail grid layout matching Channels and Library sections (resizes based on view width)
- Music history collapsed to one entry per artist+album (displays album title with "Artist - Song Title" subtitle)
- Channel-watched videos saved to history with playback position
- Play offset shown below title
- Media context IDs (movie/show/episode/artist/album/song) preserved for library-link navigation from history items
- Delete individual items: macOS context menu, iOS long-press, tvOS confirmation dialog
- Accessibility labels: "Recently played: {title}"
Streaming Architecture
- Full teardown-and-restart pattern: pause = stop + remember position; resume/seek = start at new position
- Single code path (
startPlayback()) for initial play, seek, resume, and reconnection - Mailbox-based stream ingress: packets accepted from any thread, drained by single MainActor task
- Seek-time ingress flush: pre-seek packets discarded immediately on seek
- Stale session filtering: incoming packets validated against current session ID
- StreamDataReceiver protocol: callbacks for stream initialization, video frames, audio samples, errors, and end-of-stream
- Minimum seek policy: 10-second initial start position, 10-second minimum forward seek delta
First Launch & Onboarding
- WKWebView-based tutorial on first launch (skipped on tvOS)
@AppStorage("hasShownTutorial")to show only once- macOS: "Open in Browser" button opens tutorial in Safari, min 600x400 frame
- iOS: "Open in Browser" opens in system browser
- "Done" button to dismiss
About
- App name, version, copyright, and support links (email + website)
- iOS/iPad/Mac: question mark toolbar button on server list opens About sheet
- tvOS: "About ShowShark" button on server management screen
App Updates & Cross-Promotion
- Remote app check via
https://app-check.acgao.com/check - Detects: app review mode, deprecated versions, available updates
- App update alert in root view
- "Other Apps" cross-promotion section in server list sidebar (hidden on tvOS and during App Review)
- Each promoted app shows icon, name, description; tapping opens link
Theming & Visual Design
Standard Background
- Dark mode: black base with dark red/orange accent gradient
- Light mode: light neutral with warm accent
- tvOS: slightly brighter for TV viewing distance
- visionOS: no custom background (native appearance)
Platform Styles
- Platform-specific typography (tvOS uses significantly larger sizes for 10-foot viewing)
- Platform-specific spacing for grid layout
- View modifiers for metadata labels, track cards, and picker options
Playback View Modifiers
- Status bar hidden during active playback (iOS/visionOS)
- Navigation bar and toolbar hidden during active playback (all platforms)
- Conditional application based on modal vs. navigation presentation
Accessibility
accessibilityLabelon history rows: "Recently played: {title}"- Platform-specific accessibility hints on history items
accessibilityHidden(true)on decorative elements (chevrons, icons)accessibilityElement(children: .combine)on cast member cardsaccessibilityLabelon person photos: "Photo of {name}"- Combined accessibility labels on cast cards: "{name} as {character}"
Networking Infrastructure
- Transport protocol abstraction (
ServerTransport): WebSocket (iOS/macOS/tvOS/visionOS) or HTTP long-polling (watchOS) - NWConnection-based WebSocket over TLS with
interactiveVideoservice class and TCP_NODELAY - HTTP polling transport for watchOS: POST-based send with bearer token auth, long-poll GET for server-pushed messages
- Self-signed certificate support via
sec_protocol_options_set_peer_authentication_required(false) - Request-response correlation via RequestCorrelator with timeout handling
- Protocol Buffers message serialization
- 2-second minimum interval between connection attempts to prevent server hammering
- Default port 18080, configurable range 1024-65535