Building tools. Learning to build tools. Learning to build learning tools.
Before writing a single line of Swift, it’s worth understanding why this app needed to exist at all. The Bambu Lab P2S has a perfectly usable camera. The stream is live and accessible on the local network. So why can’t you just paste the URL into VLC and call it a day? The answer involves three overlapping issues: a protocol, a certificate, and a library limitation.
RTSP (Real-Time Streaming Protocol) is a control protocol for multimedia streams, defined in RFC 2326 in 1998. Think of it like HTTP, but for video: a client sends DESCRIBE, SETUP, and PLAY commands; the server responds and then begins pushing audio/video data, typically over a separate RTP (Real-time Transport Protocol) channel.
The Bambu Lab P2S uses port 322 instead of the standard 554, and it uses RTSPS — RTSP over TLS — rather than plain RTSP. The full URL the app connects to looks like this:
rtsps://bblp:<ACCESS_CODE>@<PRINTER_IP>:322/streaming/live/1
The bblp is a fixed username, the access code is the PIN visible on the printer’s
touchscreen, and /streaming/live/1 is the stream path.
The credentials are embedded directly in the URL, which is standard RTSP authentication practice.
The “S” in RTSPS means the connection is wrapped in TLS, the same encryption layer used by HTTPS. TLS works by presenting a certificate that proves the server’s identity. Normally, that certificate is signed by a trusted Certificate Authority (CA) — organizations like Let’s Encrypt, DigiCert, or Apple’s own roots.
The Bambu Lab printer uses a self-signed certificate: a certificate it generated itself, not vouched for by any CA. From a cryptographic standpoint, the connection is still encrypted — the data on the wire is unreadable to observers. But the certificate provides no proof of identity. For a printer on your home network, that’s fine — you know which device you’re talking to. For TLS clients that follow the spec strictly, it’s a hard error.
A TLS certificate contains the server’s public key, its domain name (or IP), and a
digital signature from a CA. When you connect to https://example.com, your
browser checks: (1) Is this certificate signed by a CA I trust? (2) Does the name on the
certificate match the URL? If either check fails, you see a browser warning.
A self-signed certificate fails check (1) every time, because no CA has vouched for it.
Apple’s built-in AVPlayer (part of AVFoundation) can play RTSP streams —
sort of. It supports rtsp:// URLs on some iOS versions, but RTSPS with a self-signed
certificate is a non-starter. AVPlayer uses Apple’s TLS stack, which correctly rejects
certificates that can’t be verified. There is no public API to disable certificate validation
for AVPlayer streams. You can set NSAllowsArbitraryLoads in App Transport Security,
but that applies to HTTP(S), not to RTSP.
Even setting aside the TLS issue, AVPlayer’s RTSP support is inconsistently documented, behaves differently across iOS versions, and has been effectively abandoned in favor of HTTP Live Streaming (HLS) and MPEG-DASH for network video. Using it for a live RTSP camera feed is fighting the framework.
VLC is the Swiss Army knife of media players, and its iOS library — VLCKit — can play almost anything. RTSP, RTSPS, obscure codecs, unusual containers. It seemed like the obvious choice.
The blocker: VLCKit 3.x uses GnuTLS as its TLS implementation.
GnuTLS, when configured as VLCKit ships it, does not expose a way to disable certificate verification
for a specific connection. The tls_verify option that FFmpeg exposes (and that KSPlayer
passes through) simply doesn’t exist in the GnuTLS path VLCKit uses.
Attempts to connect the printer stream through VLCKit result in a TLS handshake failure. The only workaround would be to build VLCKit from source with a custom configuration — a significant undertaking that would need to be maintained across every VLCKit update.
TLSProxy.swift in the project is the archaeological record of a third approach:
a local TCP proxy that listens on a plain-text port, accepts RTSP from VLC, and forwards
the bytes over a TLS connection (with verification disabled at the proxy level) to the printer.
This would have worked — the proxy can use Apple’s Network framework with
NWProtocolTLS.Options and a custom verification block that always succeeds.
But it required keeping a background process alive, managing two network connections per stream,
and added failure modes. When KSPlayer’s tls_verify option solved the
problem in one line, the proxy approach was shelved.
KSPlayer is an open-source Swift video player that uses FFmpeg as its media decoding and networking backend. FFmpeg’s RTSP implementation exposes a key option:
ffplay -rtsp_transport tcp -tls_verify 0 \
'rtsps://bblp:<code>@<ip>:322/streaming/live/1'
The -tls_verify 0 flag tells FFmpeg to skip certificate validation entirely.
The connection is still encrypted — the TLS handshake still happens, keys are still
exchanged, data is still protected in transit. We’re just not verifying that the
certificate was issued by a trusted authority. For a device on your own local network
that you physically placed there, this is an acceptable tradeoff.
KSPlayer exposes FFmpeg’s avOptions dictionary directly through its
KSOptions configuration object, so this works without any custom FFmpeg build:
let options = KSOptions()
options.avOptions["tls_verify"] = "0" // Skip certificate validation
options.avOptions["rtsp_transport"] = "tcp" // Reliable TCP over UDP
That’s what the entire app is built on. The rest — the state machine, the adaptive layout, the Picture-in-Picture support — is iOS scaffolding around this core capability.
RTSP can transport RTP data over either UDP or TCP.
UDP is lower-latency in ideal conditions, but it’s connectionless — packets
can be dropped, reordered, or blocked by firewalls and NAT. TCP guarantees delivery and
ordering, which matters more than raw latency for a home network camera.
The rtsp_transport = "tcp" option forces FFmpeg to interleave RTP data
inside the existing RTSP TCP connection rather than opening a separate UDP port.
The Bambu Lab printer streams RTSPS on port 322 with a self-signed certificate.
AVPlayer has no certificate bypass API. VLCKit’s GnuTLS backend doesn’t
expose the needed option. KSPlayer’s FFmpeg backend does, via
avOptions["tls_verify"] = "0". That’s why KSPlayer is the dependency.