Start a long lived WebRTC control channel that can be used to support Trickle ICE.
This call establishes a long term encrypted WebSockets control channel for WebRTC and is designed to be used when you want a persistent control channel with support for Trickle ICE, instead of the REST API's provided in the other WebRTC endpoints. The channel established is pegged to a single AccessoryAccessory - A unique device within the system. An accessory can be any of type and is represented by a unique Accessory Id. An accessory is linked to an account via an accountId. Within this API, a singular Circle camera is an Accessory., but can live over multiple sessions.
Your WebRTC client must support Trickle ICE if you use this API call.
This call starts as an HTTP GET, but will get upgraded into a WebSockets connection either directly via a 301 Redirect or a response header.
The primary reason to use this function over the REST API based WebRTC calls is too support Trickle ICE which results in faster call establishment in most network scenarios. The WebSockets based transport will allow for streaming of the ICE candidates from the camera as they are generated.
The WebSockets channel uses a message framing layer to transport actions and their data. The actions are the same basic WebRTC commands implemented in the REST API endpoints of Get Live WebRTC Offer, Provide Live WebRTC Answer Async, Provide Live WebRTC Offer and Get Answer, and Stop Live WebRTC.
Important Notes
- Your WebSockets client MUST send Ping frames at least once an hour or the connection may drop due to the Firewall or NATs. If you are using a browser implementation this is handled automatically for you.
- If the WebSockets connection drops, timeouts or is disconnected by the server, you must reinitiate via this main API call, not the redirected URL provided.
- If you do reconnect, you must maintain any sessionId's for active calls that are still ongoing. It is possible and likely the WebControl channel can drop and re-establish without the underlying peer-to-peer call failing.
Permissions
Requires a Permissions scope of: circle:live
WebSockets Protocol
This protocol MUST be indicated using the WebSockets sub-protocol value of com.logi.circle.webrtc
uisng the text type.
Every WebSockets message of type com.logi.circle.webrtc
is comprised of a serialized JSON object with two required keys: action
and sessionId
. Additional keys may be present based on the specific action
.
Name | Type | Description |
---|---|---|
action | string | The action name of the message being sent or received. See the |
sessionId | string | The session Id this message applies too. This field is always present and contains a value except in the |
Action Types
The table below lists all of the supported action
types. The Sendable
column means that your client can send this message. The Receivable
column indicates if your client can receive this message.
Action Name | Sendable | Receivable | Description |
---|---|---|---|
yes | yes | If you send this, or set | |
yes | yes | A WebRTC Offer object is contained within this message. | |
yes | yes | A WebRTC Answer is contained within this message. | |
yes | yes | A WebRTC candidate is contained within this message. | |
yes | yes | Request to end an established WebRTC call. |
requestOffer Action Type
The requestOffer
action type requests the receiver to begin the process of generating an offer. This action is implied automatically at the server if the query param requestOffer
is true
. Using the query param is highly encouraged as it reduces the overall call setup latency when having the camera generate the initial Offer. The additional keys of this call contain the same audio
and video
entries as the query param list.
In battery powered cameras that have a wake-up delay, the Circle servers may respond to this message with its own requestOffer
to your client so that your client can begin the ICE candidate generation process while the camera is waking up from low-power sleep. This leads to a significant lower latency during call setup.
The other reason your client may send this action is to establish a new call to the same AccessoryAccessory - A unique device within the system. An accessory can be any of type and is represented by a unique Accessory Id. An accessory is linked to an account via an accountId. Within this API, a singular Circle camera is an Accessory. after an end action already occured.
Name | Type | Sendable | Receivable | Description |
---|---|---|---|---|
audio | string | yes | yes | What directionality should the audio stream have. Valid values are: |
video | string | yes | yes | What directionality should the video stream have. Valid values are: |
iceTransportPolicy | string | no | yes | See |
iceServers | array of objects | no | yes | See |
offer Action Type
The offer
action type is used to convey the WebRTC Offer. The same payload fields as the response for Get Live WebRTC Offer. If your client side is providing the offer
as is done in Provide Live WebRTC Offer and Get Answer, then you must provide valid iceServers
or otherwise only host candidates can be generated. Logitech does not export an API for just requesting iceServers
, and they are only returned when using the requestOffer action type.
A session renegotiation can be initiated by sending a new offer
action type for an existing sessionId
. This is equivalent to the Renegotiate Live WebRTC REST call.
If offer
is the first message in the call sequence (from either side), then the sessionId
value it presents will be used for the rest of the sessions life-cycle. If your client originates the offer, you can pass whatever string based value you want for sessionId
.
Name | Type | Description |
---|---|---|
sdp | string | The full WebRTC compliant SDP blob. This should be passed directly to the Javascript or Native API's which consume Offers such as RTCPeerConnection.setRemoteDescription() |
iceTransportPolicy | string | The current ICE transport policy. This field must be set as the iceTransportPolicy key within the RTCConfiguration object passed to the RTCPeerConnection() constructor in your WebRTC stack.Valid values are:
|
iceServers | array of objects | An array of iceServers and its configuration. This field must be set as the iceServers key within the RTCConfiguration object passed to the RTCPeerConnection() constructor in your WebRTC stack. |
answer Action Type
The answer
action type is used to convey the WebRTC Answer. The payload contains the same fields as the request in Provide Live WebRTC Answer Async
Name | Type | Description |
---|---|---|
sdp | string | The full WebRTC compliant SDP blob. |
iceCandidate Action Type
The iceCandidate
action type is used to transmit a new ICE Candidate as part of the Trickle ICE process. This message serves as a conduit for both sides to exchange new candidates as they become available. The object contains the same members as the RTCIceCandidateInit object and is meant to be fed directly to the RTCPeerConnection.addIceCandidate() function in Javascript.
Name | Type | Description |
---|---|---|
candidate | string | The ICE candidate value. |
sdpMLineIndex | integer or null | The ICE SDP Media Line Index this candidate belongs to. |
sdpMid | string or null | The ICE SDP media ID this candidate refers too. |
usernameFragment | string or null | The ICE username fragment for this candidate. |
end Action Type
The end
action type is used to indicate a request to terminate the call. If you receive this message, the far side initiated the termination. If you send this message you must supply a reason
field. This action type is equivalent to calling Stop Live WebRTC.
Name | Type | Description |
---|---|---|
reason | string | The reason for the call ending. Must be one of: |
Sample Messages
websocket = new WebSocket("wss://api.circle.logi.com/api/accessories/70e3e6f9-70c3-45b2-62e4-ace3d027988a/live/webrtc/session?requestOffer=true", "com.logi.circle.webrtc");
websocket.onopen = function(evt) {
console.log("Socket is open");
};
websocket.onerror = function(evt) {
console.log("Socket Error: " + evt.data);
};
websocket.onmessage = function(event) {
if(typeOf event.data != "string" ) {
console.log("Error. Bad message received. No data: " + event);
return;
}
//create a JSON object
var jsonObject = JSON.parse(event.data);
if (typeOf jsonObject.action != 'string' || typeOf jsonObject.sessionId != 'string')
{
console.log("Error. Bad message received. Missing required keys: " + event.data);
return;
}
// Valid object found with the required keys.
var action = jsonObject.action;
var sessionId = jsonObject.sessionId;
console.log("Received the message: " + jsonObject);
}
function sendMessage(actionName, sessionId, actionData) {
var dataToSend = {
action:actionName,
sessionId: sessionId,
...actionData
}
websocket.send(JSON.stringify(dataToSend));
}
function closeSocket() {
websocket.close();
}
// 1. requestOffer - Sent from your client to the server.
{
"action":"requestOffer",
"sessionId": "",
"audio": "sendrecv",
"video": "sendonly"
}
// 2. requestOffer - Sent back from our server because the camera is in deep-sleep.
{
"action":"requestOffer",
"sessionId": "",
"audio": "sendrecv",
"video": "sendonly",
"iceTransportPolicy": "all",
"iceServers": [
{
"urls": ["stun:stun.video.logi.com:19302"]
},
{
"urls": ["turns:node-i-0af43d950d489ce80.video.logi.com:443?transport=tcp"],
"username": "fasf09832t78yasdnmskjhasd",
"credential": "aslkjfs83qn6nkangjsddsd"
}
],
}
// offer sent from the camera
{
"action": "offer",
"sessionId": "623837789654644768",
"sdp": "v=0 .... Rest of SDP Payload ....",
"iceTransportPolicy": "all",
"iceServers": [
{
"urls": ["stun:stun.video.logi.com:19302"]
},
{
"urls": ["turns:node-i-0af43d950d489ce80.video.logi.com:443?transport=tcp"],
"username": "fasf09832t78yasdnmskjhasd",
"credential": "aslkjfs83qn6nkangjsddsd"
}
]
}
// new candidate with the data being the Opaque RTCIceCandidate dictionary
{
"action": "iceCandidate",
"sessionId": "623837789654644768",
"candidate":"candidate:829056235 1 udp 2122260223 192.168.100.28 53618 typ host generation 0 ufrag NgCj network-id 1 network-cost 10",
"sdpMLineIndex":0,
"sdpMid":"audio",
"usernameFragment": "NgCj"
}
// answer sent from your client
{
"action": "answer",
"sessionId": "623837789654644768",
"sdp": "v=0 .... Rest of SDP Payload ...."
}
// close sent from your client
{
"action": "end",
"sessionId": "623837789654644768",
"reason": "hangup"
}
WebSockets Close Event
The com.logi.circle.webrtc
WebSockets sub-protocol defines some application level close status codes that your client must implement properly.
Code | Sendable | Receivable | Description |
---|---|---|---|
1013 | no | yes | The server wants you to reconnect after a few seconds of delay since the camera is moving to a new server. This is the WebSockets equivalent of the 410 - Gone. |
4000 | no | yes | Bad request - Your client sent a malformed message or bad JSON frame. This is the WebSockets equivalent of the 400 - Bad Request. This doesn't invalidate any established sessionId's. |
4001 | yes | no | Your client is disconnecting, but don't implicitly disconnect any underlying sessions that you started. |
4002 | yes | yes | Normal user initiated close. |