Building tools. Learning to build tools. Learning to build learning tools.
Getting the stream to open at all — overcoming the self-signed certificate —
was the primary challenge. But there’s a secondary challenge: making the stream
feel live. A 3D printer camera is for monitoring. If the feed is 10 seconds
behind reality, you can’t tell whether the print just failed or whether that was
yesterday. The streaming options in CameraView are a deliberate set of
tradeoffs tuned for minimum latency.
All KSPlayer configuration flows through a KSOptions instance passed to
KSPlayerLayer:
let options = KSOptions()
options.avOptions["rtsp_transport"] = "tcp"
options.avOptions["tls_verify"] = "0"
options.nobuffer = true
options.codecLowDelay = true
options.maxAnalyzeDuration = 1_000_000
options.probesize = 500_000
options.registerRemoteControll = false
options.canStartPictureInPictureAutomaticallyFromInline = true
avOptions is a [String: Any] dictionary passed directly to FFmpeg’s
AVFormatContext as RTSP protocol options. The other properties are KSPlayer-level
settings that control its own buffering and decode pipeline. Let’s go through each one.
RTSP uses a separate channel to carry the actual video data (RTP packets). By default, FFmpeg tries UDP for this channel, because UDP has lower overhead than TCP. But UDP is a fire-and-forget protocol — packets can be lost, duplicated, or arrive out of order.
On a home network, UDP is generally fine. But there are common scenarios where it fails:
Setting rtsp_transport = "tcp" tells FFmpeg to interleave the RTP video
data inside the existing RTSP TCP connection, rather than opening a separate UDP port.
This is slower in theory, but far more reliable in practice for LAN camera monitoring.
As established in Section 1, this is the whole reason KSPlayer was chosen:
options.avOptions["tls_verify"] = "0"
This passes FFmpeg’s tls_verify option to the TLS layer, disabling
certificate chain verification. The TLS handshake still occurs — encryption keys
are negotiated, traffic is encrypted — but the server’s certificate is not
checked against any trust store.
Disabling TLS certificate verification opens the connection to a man-in-the-middle attack: someone on the same network could intercept the connection and present their own certificate, and the app would accept it. For a device on your home LAN, this risk is generally acceptable — you control the network. If you were connecting over the internet, or on an untrusted network, certificate pinning would be the right approach instead. Certificate pinning means embedding the printer’s specific public key in the app and validating against that, rather than the CA chain.
Video players buffer incoming data by default. Buffering smooths over network jitter — if packets arrive unevenly, the player drains from the buffer at a steady rate, so playback doesn’t stutter. This is great for watching a movie over a slow connection. For a printer camera, it introduces unnecessary delay.
options.nobuffer = true
options.codecLowDelay = true
nobuffer = true tells KSPlayer to minimize buffering and
present frames as soon as they arrive. This is analogous to FFmpeg’s
-fflags nobuffer flag.
codecLowDelay = true instructs the H.264 decoder to
output frames as they’re decoded rather than waiting for B-frame reordering.
H.264 can encode frames out of display order (B-frames reference future frames).
Waiting for the full group of pictures before displaying any of them adds latency.
Low-delay mode trades some decode efficiency for immediate output.
The combined effect of these two settings typically reduces end-to-end latency from 2–5 seconds (default buffering) to under 1 second on a good Wi-Fi connection.
When FFmpeg opens a stream, it spends time probing: reading a chunk of data to figure out the stream’s format, codec, bitrate, and timing information. This probing is why there’s a delay between “connecting” and “first frame.”
options.maxAnalyzeDuration = 1_000_000 // microseconds = 1 second
options.probesize = 500_000 // bytes
The default values are much larger — FFmpeg may analyze up to 5–10 seconds of data before deciding a stream is ready. Since the Bambu Lab stream is a known-format H.264 feed, we can afford to cut this short:
maxAnalyzeDuration limits how much time (in microseconds) FFmpeg
spends analyzing. 1,000,000 µs = 1 second.probesize limits how many bytes it reads during analysis. 500,000
bytes = ~500 KB.Together, these settings tell FFmpeg: “make a decision within 1 second using at most 500 KB of probe data.” For a reliable H.264 RTSP source, this is always sufficient. The result is noticeably faster stream startup.
options.registerRemoteControll = false // note: KSPlayer misspells "control"
iOS has a “Now Playing” system that shows media controls on the Lock Screen and in Control Center when any audio or video is playing. For a music or podcast app, this is essential. For a printer camera feed, it’s odd and distracting — you don’t want your printer to appear as a media source that you can “pause” from the Lock Screen.
Setting registerRemoteControll = false tells KSPlayer not to register
with the Remote Command Center, keeping the printer stream invisible to the system
media controls.
The streaming options are a deliberate stack of latency and reliability tradeoffs.
TCP transport avoids UDP firewall issues. tls_verify: 0 handles the
self-signed certificate. nobuffer and codecLowDelay minimize
end-to-end latency. Reduced probe settings speed up stream startup. The result is a
stream that typically goes live in under two seconds and stays within a second of
real-time.