VideoCall
VideoCall connects two browsers for a one-to-one call, with the media going through the server. Each user registers a username that works as a phone number. One user calls another by name, the callee accepts, and the plugin relays audio, video and data in both directions. Use it for a simple call or support line; for more than two people use VideoRoom.
Enable and configure
VideoCall is not in the default build. Build janus with --features plugin-videocall. The plugin has no settings except whether it reports events to event handlers:
general: {
#events = false # true|false: send events to event handlers (default true)
}See the plugin files reference.
Try it
Every VideoCall request is asynchronous: the POST returns an ack and the answer is an event on the same handle. The event body is always {"videocall":"event","result":{…}} or, for an error, {"videocall":"event","error_code":N,"error":"…"}.
Register and list
// VideoCall registration. Every request is asynchronous: the POST gets an ack and the result is an event.
// Produced by docs-site/scripts/record-plugins-core.mjs against the Rust binary. alice and carol are two sessions.
// → POST /janus
{
"janus": "create",
"transaction": "alice-1"
}
// ← 200 OK
{
"janus": "success",
"transaction": "alice-1",
"data": {
"id": 9873482636819065
}
}
// → POST /janus/{session_id}
{
"janus": "attach",
"plugin": "janus.plugin.videocall",
"transaction": "alice-2"
}
// ← 200 OK
{
"janus": "success",
"session_id": 9873482636819065,
"transaction": "alice-2",
"data": {
"id": 9728245592380508
}
}
// call before register (error 473). C then also drops the handle's PeerConnection, which is why a hangup event for the handle follows.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-3",
"body": {
"request": "call",
"username": "bob"
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 4661856518455979478 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-3"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-3",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 473,
"error": "Register a username first"
}
}
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "hangup",
"session_id": 9873482636819065,
"sender": 9728245592380508,
"reason": "Close PC"
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-4",
"body": {
"request": "list"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-4"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-4",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"list": []
}
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-5",
"body": {
"request": "register",
"username": "alice"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-5"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-5",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "registered",
"username": "alice"
}
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-6",
"body": {
"request": "register",
"username": "alice"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-6"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-6",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 477,
"error": "Already registered (alice)"
}
}
}
// another handle cannot take the name
// → POST /janus
{
"janus": "create",
"transaction": "carol-1"
}
// ← 200 OK
{
"janus": "success",
"transaction": "carol-1",
"data": {
"id": 4672383433022915
}
}
// → POST /janus/{session_id}
{
"janus": "attach",
"plugin": "janus.plugin.videocall",
"transaction": "carol-2"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4672383433022915,
"transaction": "carol-2",
"data": {
"id": 1406700319406760
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "carol-3",
"body": {
"request": "register",
"username": "alice"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4672383433022915,
"transaction": "carol-3"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 4672383433022915,
"transaction": "carol-3",
"sender": 1406700319406760,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 476,
"error": "Username 'alice' already taken"
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "carol-4",
"body": {
"request": "register",
"username": "carol"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4672383433022915,
"transaction": "carol-4"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 4672383433022915,
"transaction": "carol-4",
"sender": 1406700319406760,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "registered",
"username": "carol"
}
}
}
}
// list shows every registered name. The port sorts the names; the C server returns them in hash-table order, so do not rely on the order.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-7",
"body": {
"request": "list"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-7"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-7",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"list": [
"alice",
"carol"
]
}
}
}
}
// call yourself, and call without an offer
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-8",
"body": {
"request": "call",
"username": "alice"
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 4661856518455979478 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-8"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-8",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 479,
"error": "You can't call yourself... use the EchoTest for that"
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-9",
"body": {
"request": "call",
"username": "carol"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-9"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-9",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 482,
"error": "Missing SDP"
}
}
}
// call a name nobody holds, accept with nothing ringing, unknown and malformed requests. Request names are case-insensitive.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-10",
"body": {
"request": "call",
"username": "nobody"
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 4661856518455979478 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "error",
"session_id": 9873482636819065,
"transaction": "alice-10",
"error": {
"code": 490,
"reason": "Error setting ICE locally"
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-11",
"body": {
"request": "accept"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-11"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-11",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 481,
"error": "No incoming call to accept"
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-12",
"body": {
"request": "Bogus"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-12"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-12",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 472,
"error": "Unknown request (Bogus)"
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-13",
"body": {
"request": 5
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-13"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-13",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 474,
"error": "Invalid element type (request should be a string)"
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-14",
"body": {
"request": "register"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-14"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-14",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"error_code": 475,
"error": "Missing mandatory element (username)"
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-15",
"body": {
"request": "LIST"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-15"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-15",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"list": [
"alice",
"carol"
]
}
}
}
}
// → POST /janus/{session_id}
{
"janus": "destroy",
"transaction": "alice-16"
}
// ← 200 OK
{
"janus": "success",
"session_id": 9873482636819065,
"transaction": "alice-16"
}The names are held by the handle: there is no unregister, and closing the handle frees the name.
Call, accept, hang up
This transcript is the whole signalling of a call, run against the Rust binary with a hand-made, shortened SDP. Bob's answer is built from Alice's offer by the script, so no media flows. It shows who gets which event, the jsep that travels with incomingcall and accepted, the User busy case, set and hangup.
// A call between alice and bob: call, incomingcall, accept, accepted, set, hangup. The SDP is shortened and bob's answer is built from alice's offer by the script; no media flows.
// Produced by docs-site/scripts/record-plugins-core.mjs against the Rust binary.
// → POST /janus
{
"janus": "create",
"transaction": "alice-1"
}
// ← 200 OK
{
"janus": "success",
"transaction": "alice-1",
"data": {
"id": 9873482636819065
}
}
// → POST /janus/{session_id}
{
"janus": "attach",
"plugin": "janus.plugin.videocall",
"transaction": "alice-2"
}
// ← 200 OK
{
"janus": "success",
"session_id": 9873482636819065,
"transaction": "alice-2",
"data": {
"id": 9728245592380508
}
}
// → POST /janus
{
"janus": "create",
"transaction": "bob-1"
}
// ← 200 OK
{
"janus": "success",
"transaction": "bob-1",
"data": {
"id": 4661856518455979
}
}
// → POST /janus/{session_id}
{
"janus": "attach",
"plugin": "janus.plugin.videocall",
"transaction": "bob-2"
}
// ← 200 OK
{
"janus": "success",
"session_id": 4661856518455979,
"transaction": "bob-2",
"data": {
"id": 5783672383433022
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-3",
"body": {
"request": "register",
"username": "alice"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-3"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-3",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "registered",
"username": "alice"
}
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "bob-3",
"body": {
"request": "register",
"username": "bob"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4661856518455979,
"transaction": "bob-3"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 4661856518455979,
"transaction": "bob-3",
"sender": 5783672383433022,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "registered",
"username": "bob"
}
}
}
}
// alice calls bob. alice gets calling; bob gets incomingcall with alice's offer.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-4",
"body": {
"request": "call",
"username": "bob"
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 9150406700319406760 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-4"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-4",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "calling"
}
}
}
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 4661856518455979,
"sender": 5783672383433022,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "incomingcall",
"username": "alice"
}
}
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 9150406700319406760 2 IN IP4 192.168.1.10\r\ns=-\r\n…"
}
}
// bob accepts with his answer. Both sides get accepted; alice's carries bob's answer.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "bob-4",
"body": {
"request": "accept"
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 8492722295216744922 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 4661856518455979,
"transaction": "bob-4"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "accepted",
"username": "bob"
}
}
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 8492722295216744922 2 IN IP4 192.168.1.10\r\ns=-\r\n…"
}
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 4661856518455979,
"transaction": "bob-4",
"sender": 5783672383433022,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "accepted"
}
}
}
}
// A third user who calls someone who is already in a call gets a hangup result with the reason User busy.
// → POST /janus
{
"janus": "create",
"transaction": "carol-1"
}
// ← 200 OK
{
"janus": "success",
"transaction": "carol-1",
"data": {
"id": 5036659394255587
}
}
// → POST /janus/{session_id}
{
"janus": "attach",
"plugin": "janus.plugin.videocall",
"transaction": "carol-2"
}
// ← 200 OK
{
"janus": "success",
"session_id": 5036659394255587,
"transaction": "carol-2",
"data": {
"id": 9089821330274292
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "carol-3",
"body": {
"request": "register",
"username": "carol"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 5036659394255587,
"transaction": "carol-3"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 5036659394255587,
"transaction": "carol-3",
"sender": 9089821330274292,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "registered",
"username": "carol"
}
}
}
}
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "carol-4",
"body": {
"request": "call",
"username": "bob"
},
"jsep": {
"type": "offer",
"sdp": "v=0\r\no=- 9150406700319406760 2 IN IP4 127.0.0.1\r\ns=-\r\n…"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 5036659394255587,
"transaction": "carol-4"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 5036659394255587,
"transaction": "carol-4",
"sender": 9089821330274292,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "hangup",
"username": "carol",
"reason": "User busy"
}
}
}
}
// set changes the settings of this side's contribution.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-5",
"body": {
"request": "set",
"audio": false,
"bitrate": 128000
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-5"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-5",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "set"
}
}
}
}
// alice hangs up with a reason. bob gets that reason (without one: We did the hangup). alice gets her own hangup event, and its reason is always Explicit hangup: C overwrites it, and the port does the same.
// → POST /janus/{session_id}/{handle_id}
{
"janus": "message",
"transaction": "alice-6",
"body": {
"request": "hangup",
"reason": "done talking"
}
}
// ← 200 OK
{
"janus": "ack",
"session_id": 9873482636819065,
"transaction": "alice-6"
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 4661856518455979,
"sender": 5783672383433022,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "hangup",
"username": "alice",
"reason": "done talking"
}
}
}
}
// ⇠ event, pushed by the server (long poll of GET /janus/{session_id})
{
"janus": "event",
"session_id": 9873482636819065,
"transaction": "alice-6",
"sender": 9728245592380508,
"plugindata": {
"plugin": "janus.plugin.videocall",
"data": {
"videocall": "event",
"result": {
"event": "hangup",
"username": "alice",
"reason": "Explicit hangup"
}
}
}
}In a browser, the same sequence follows accepted with trickle messages from both sides, then webrtcup and media events on each handle. The Bench check videocall.call does that in headless Chrome and passes (register, call, accept, media both ways, hangup).
Reference
Requests
| Request | Parameters | Needs jsep | Result event |
|---|---|---|---|
list | none | no | result.list: array of registered usernames |
register | username (string, required) | no | result: {"event":"registered","username":"…"} |
call | username (required): who to call | an offer, required | caller: result: {"event":"calling"}. Callee: result: {"event":"incomingcall","username":"<caller>"} with the caller's offer in jsep. |
accept | none | an answer | caller: result: {"event":"accepted","username":"<callee>"} with the answer in jsep. Callee: result: {"event":"accepted"}. |
set | audio, video (booleans: relay or drop), bitrate (cap in bits per second, REMB), record, filename, restart, and for simulcast substream, temporal, fallback | an offer or answer, only to renegotiate | result: {"event":"set"}. By the C documentation the other side gets result: {"event":"update"} when a renegotiation jsep came with it (not exercised here). |
hangup | reason (string, optional) | no | See below. |
Request names are matched without regard to case: LIST works (verified).
set applies to what you send, not to the whole call. With record: true only your own media is written (<filename>-audio.mjr, -video.mjr, -data.mjr); to record both sides, both users send record: true.
Hangup events
Whatever ends the call, both sides get {"event":"hangup","username":U,"reason":R}:
| Cause | The other side gets | The one who ended it gets |
|---|---|---|
hangup request | username of who hung up, reason as sent (default We did the hangup) | username of its own, reason: "Explicit hangup" always |
| The PeerConnection closes | reason: "Remote WebRTC hangup" | |
| A call to a user who is in a call | reason: "User busy" (verified) |
C builds the reason you pass and then overwrites the reply to the caller with Explicit hangup; the port does the same, so a reason you pass reaches only the other side (verified in the transcript above).
Error codes
| Code | Text (as the server sends it) | When |
|---|---|---|
| 470 | No message?? | No message at all. |
| 471 | JSON error: not an object | The body is not a JSON object. |
| 472 | Unknown request (<name>) | Unrecognised request. |
| 473 | Register a username first | call before register. |
| 474 | Invalid element type (… should be a string) | A parameter of the wrong type. |
| 475 | Missing mandatory element (<name>) | A required parameter is absent. |
| 476 | Username 'x' already taken | Another handle holds the name. |
| 477 | Already registered (x) | This handle already has a name. |
| 478 | Username 'x' doesn't exist | call to an unknown name. |
| 479 | You can't call yourself... use the EchoTest for that | call to your own name. |
| 480 | Already in a call | call while in a call. |
| 481 | No incoming call to accept | accept with nothing ringing. |
| 482 | Missing SDP | call or accept without jsep. |
| 483 | Error parsing offer: … or Error parsing answer: … | The SDP does not parse. |
The texts of 470, 471, 480 and 483 are from the C source; the other codes appear in the transcripts above.
Behaviour you may not expect
- Before you register,
callfails with 473 and then the server drops the handle's PeerConnection: a{"janus":"hangup","reason":"Close PC"}event follows.setandhangupare accepted without a registration (hangupanswershangupwith nousername). This matches the C captures. - The
listis sorted by name in the port. C returns hash-table order. Do not depend on it (listed as a deviation inPARITY_LOG.md). - A busy callee is not an error: the caller gets a normal
hangupresult withreason: "User busy". - The signalling does not wait for media:
acceptedarrives before ICE finishes.
Known gaps
- Simulcast: the keyframe gate, substream and temporal-layer selection are ported and unit tested but not exercised with a browser (gap).
- Recording and
restartrenegotiation (updateevent) were not exercised here. They are written from the C source and the port's unit tests. - C has no plugin-specific Admin API message for VideoCall, and the port has none either; the plugin's per-handle data shows in
handle_info.
Test it in Janus Bench
Open the VideoCall page, or run the check videocall.call from Run all checks (passes). See Janus Bench.