ICE Server (STUN/TURN) Usage
To establish a WebRTC media connection, the application must configure an RTCPeerConnection
, the core WebRTC interface defined by the WebRTC standard and implemented in most modern browsers and native libraries. This object manages the media path between peers and requires a list of ICE servers for NAT traversal.
If using Nabto WebRTC ICE, the Nabto WebRTC SDK provides the necessary ICE server configuration as part of the connection setup. This includes STUN/TURN server URLs and credentials. After receiving this configuration from the SDK, the application must pass it to the RTCPeerConnection
constructor. This is the only step where the application interacts with ICE server details:
extension RTCConnectionHandler: MessageTransportObserver {
func messageTransport(
_ transport: any MessageTransport,
didFinishSetup iceServers: [SignalingIceServer]
) async {
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
let config = RTCConfiguration()
config.iceServers = iceServers.map { iceServer in
RTCIceServer(
urlStrings: iceServer.urls,
username: iceServer.username,
credential: iceServer.credential
)
}
peerConnection = factory.peerConnection(
with: config,
constraints: constraints,
delegate: self
)
}
// ... remaining MessageTransportObserver callbacks
}
class RTCConnectionHandler {
// ...
private MessageTransport messageTransport;
private void setupMessageTransport() {
messageTransport.addObserver(new MessageTransport.AbstractObserver() {
@Override
public void onWebrtcSignalingMessage(WebrtcSignalingMessage message) {
super.onWebrtcSignalingMessage(message);
// Handle message here
}
@Override
public void onSetupDone(List<SignalingIceServer> iceServers) {
super.onSetupDone(iceServers);
setupPeerConnection(iceServers);
}
});
}
private void setupPeerConnection(List<SignalingIceServer> iceServers) {
var rtcIceServers = convertSignalingIceServersToPeerConnectionIceServers(iceServers);
var rtcConfig = new PeerConnection.RTCConfiguration(rtcIceServers);
var observer = new PeerConnection.Observer() {
// PeerConnection.Observer implementation
};
peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, observer);
}
private List<PeerConnection.IceServer> convertSignalingIceServersToPeerConnectionIceServers(List<SignalingIceServer> iceServers) {
return iceServers.stream().map(iceServer -> {
var builder = PeerConnection.IceServer.builder(iceServer.urls);
if (iceServer.username != null) { builder.setUsername(iceServer.username); }
if (iceServer.credential != null) { builder.setPassword(iceServer.credential); }
return builder.createIceServer();
}).collect(Collectors.toList());
}
}
this.messageTransport.on("setupdone", async (iceServers) => {
this.pc = new RTCPeerConnection({ iceServers: iceServers });
//...
})
messageTransport_->addSetupDoneListener(
[self](const std::vector<nabto::webrtc::IceServer> &iceServers) {
self->createPeerConnection(iceServers);
});
If not using the Nabto WebRTC Managed ICE Service, the application must obtain an alternative ICE server configuration, e.g. using customer hosted ICE servers.
Under the hood the client side Nabto WebRTC SDK receives managed ICE server configuration from the device (camera). If it has been authorized by the Nabto Signaling Service to obtain TURN credentials (ie, if using the Nabto managed ICE services), the credentials are passed on from device to the client SDK and to the application through the setupdone
callback.
It is also possible for centrally authorized clients to directly obtain ICE server configurations from the Nabto WebRTC Signaling Service.