Building tools. Learning to build tools. Learning to build learning tools.
What FredCam is, what you’ll learn, and how this tutorial is organized.
FredCam is an iOS app that streams the live camera feed from a Bambu Lab P2S 3D printer to your iPhone or iPad. The printer streams over RTSPS — a TLS-encrypted variant of the Real-Time Streaming Protocol — using a self-signed certificate that most media players refuse to touch. FredCam works around that constraint to deliver a live feed you can monitor from across the house, complete with Portrait/landscape layout adaptation and Picture-in-Picture so you can watch the bed level while reading the next step of a recipe.
The app is small — six Swift files, around 650 lines of code total — but it packs in a surprising number of real-world iOS concepts: bridging UIKit into SwiftUI, state machines, reactive data models, adaptive layout, Picture-in-Picture, and the full dependency decision-making process including license implications. That’s what this tutorial is really about.
This tutorial teaches three subjects in parallel:
| iOS iOS & SwiftUI | Network Networking | Arch Architecture |
|---|---|---|
| ObservableObject — Swift’s reactive data model for driving UI from persistent state. | RTSP / RTSPS — The streaming protocol used by IP cameras, and why the “S” creates problems. | State machines — Modeling idle / connecting / live / error as a Swift enum with associated values. |
| UIViewRepresentable — The bridge that lets you embed any UIKit view inside a SwiftUI hierarchy. | TLS and self-signed certificates — Why the printer’s certificate is rejected, and how FFmpeg bypasses the check. | Coordinator pattern — Routing delegate callbacks from UIKit back into SwiftUI state. |
| Picture-in-Picture — Keeping the video feed alive while you switch apps. | FFmpeg avOptions — Low-level transport and buffering knobs exposed through KSPlayer. | Stable view identity — Why moving a view in the SwiftUI tree tears down and recreates it, and how to avoid that. |
| verticalSizeClass — Detecting landscape vs. portrait for adaptive layout. | Local network permissions — iOS 14+ requirements for mDNS and local TCP connections. | Dependency decisions — How to evaluate libraries: capability, license, maintainability, and what you’re signing up for. |
You need:
brew install xcodegen. This tool generates
FredCam.xcodeproj from the project.yml source-of-truth file.
Section 2 covers why this is worthwhile.
ffplay on a Mac
if you have the printer IP and access code.
If you’ve already read the spectrum analyzer tutorial,
much of the SwiftUI groundwork (stacks, modifiers, @State, @main) is already familiar.
This tutorial focuses more on the inter-layer bridging and dependency decisions than on SwiftUI basics.
You can skim the iOS-tagged sections if you’re comfortable with SwiftUI.
Sections are tagged with one of three labels:
Unlike a build-along tutorial where you type code incrementally, this tutorial explains an existing app. Every section walks through real code from the FredCam source, explains each decision, and highlights the tradeoffs. You’re encouraged to have the source open in Xcode alongside these pages.
The entire app lives in six Swift files. Here’s the map before we dive in:
| File | Lines | Role |
|---|---|---|
FredCamApp.swift |
10 | App entry point. Creates the WindowGroup containing ContentView. |
PrinterSettings.swift |
25 | Persistent data model. Stores printer IP and access code in UserDefaults. Builds the RTSPS URL. |
CameraView.swift |
96 | The UIKit–SwiftUI bridge. Wraps KSPlayer inside a UIViewRepresentable and routes player events back into StreamState. |
SettingsView.swift |
77 | Sheet-based UI for entering the printer IP and access code. |
ContentView.swift |
310 | Main orchestrator. Defines StreamState, controls the view hierarchy, handles orientation, and renders all overlays. |
TLSProxy.swift |
125 | Unused reference implementation. A local TCP proxy that tunnels plain-text RTSP over TLS. Kept for documentation value. |
The TLSProxy.swift file is an interesting artifact. It was written as an alternative approach
— a local proxy that would accept plain RTSP and forward it over TLS, which would have let even
VLC-based players work. It turned out to be unnecessary once KSPlayer’s direct tls_verify: 0
option was discovered. The file was kept because architectural dead ends are worth documenting;
the next person to solve a similar problem might find the approach useful.