Calls, group calls and the call screen
- Calls between two people ring in every tab the callee has open, and stop ringing everywhere once one answers. They run directly between the two browsers, through TURN when a network needs it, or move into a room on WordPlus Cloud's media servers when nothing else works.
- Group calls are rooms on WordPlus Cloud's media servers: 32 people with video, 50 audio only.
- The call screen is a drop-in interface for both: call buttons, the ring and a call window, with no call code.
In this version, calls take integer user ids, as the realtime server's relay does. A user whose id is a string (a UUID) is refused calls with no_user; channels and presence take any id.
Who may call
Your server owns call and room names by their start, as it owns channels:
- Node
- PHP
- Python
realtime.calls( 'support-', async ( user, call, withUser ) => {
if ( withUser === null ) return isStaff( user ) ? { admin: true } : false; // a room: the staff only
return isStaff( user ) || isStaff( withUser ); // a call: with an agent only
} );
// Laravel: the same with the Realtime facade, in a service provider's boot().
$realtime->calls( 'support-', function ( $user, $call, $with ) {
if ( null === $with ) {
return is_staff( $user ) ? [ 'admin' => true ] : false; // a room: the staff only
}
return is_staff( $user ) || is_staff( $with ); // a call: with an agent only
} );
def may_call_support(user, call, with_user):
if with_user is None:
return {"admin": True} if is_staff(user) else False # a room: the staff only
return is_staff(user) or is_staff(with_user) # a call: with an agent only
realtime.calls("support-", may_call_support)
withUser($with,with_user) is the other person of a call between two (whomusercalls, or who callsuser), ornullfor a room.- Answer
falsefor no,truefor yes, or an object:info(what the others see ofuser: your profile's name and avatar by default), and for a roompublishandsubscribe(true unless said),adminandhidden(false unless said). - Your server asks again when the callee answers, with the callee as
user.
Calling
const calls = await rt.calls(); // loads the call engine the first time
const call = await calls.start( 'support-42', { to: 34, type: 'video' } );
call.on( 'state', ( state ) => show( state ) ); // ringing, connecting, connected, reconnecting, ended
call.on( 'remote', ( remote ) => remote.camera?.attach( video ) );
await call.setTrack( 'camera', cameraTrack );
await call.setTrack( 'microphone', micTrack );
call.end();
calls.on( 'incoming', async ( incoming ) => {
// incoming.from, incoming.info ( { name, avatar } ), incoming.type
const call = await incoming.accept(); // or incoming.decline( 'busy' )
} );
- A page holds its user's line, where their calls ring, unless
connect()was givenring: false. - A call rings for 45 seconds, then ends with
no_answer. - Each side's connection may drop for 45 seconds during a call and come back.
Group calls
const room = await calls.room( 'support-staff', { type: 'video' } );
room.on( 'person', ( person ) => person.camera?.attach( tileFor( person ) ) ); // { user, info, camera, microphone, screen, speaking }
room.on( 'person_left', ( person ) => removeTile( person ) );
await room.setTrack( 'camera', cameraTrack );
room.leave();
Rooms need the room client served by your app (rooms: '/realtime-rooms.min.js' in connect(); npx wordplus-realtime copy public/ puts it there).
The call screen
await rt.callScreen(); // the call window and the ring, on this page
<wordplus-call to="34" call="support-42" type="video" name="Ann" avatar="https://…/ann.jpg">Call Ann</wordplus-call>
<wordplus-room room="support-staff" type="video" title="Staff room">Join the staff room</wordplus-room>
- A call rings only on pages that start the screen (or your own calls code): start it on every page a person may be called on.
- Its words:
rt.callScreen( { strings: { … } } ), every key of the screen's strings, in your page's language. - Its colours are CSS custom properties a page sets; it lives in a shadow root, so your styles don't reach in.
- In React:
<CallScreen />,<CallButton>and<RoomButton>(React).
Why a call ended
call.endReason | |
|---|---|
hangup | a side hung up |
cancelled | the caller hung up before an answer |
rejected, busy | the callee declined, or said they were busy |
no_answer | nobody answered within 45 seconds |
page_closed | a side closed its page |
peer_lost | a side's connection didn't come back within 45 seconds |
room_lost | the room it moved to gave up |
not_included, forbidden, bad_token, expired | the server refused to start it (errors) |
no_call, call_ended | answered after the caller had given up |
signaling_unavailable | the page's connection was down when it started |
What WordPlus Cloud sees
- Your server decides every call: WordPlus Cloud takes only tokens it signed, for the people and the call it named.
- The two browsers' negotiation is sealed with a key your server derives from its local key, which WordPlus Cloud never sees.
- The media is encrypted end to end by WebRTC on a direct or relayed path. In a room, the media server forwards it.