---
sidebar_position: 5
---

# 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**

```ts
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
} );
```

**PHP**

```php
// 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
} );
```

**Python**

```python
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 (whom `user` calls, or who calls `user`), or `null` for a room.
- Answer `false` for no, `true` for yes, or an object: `info` (what the others see of `user`: your profile's name and avatar by default), and for a room `publish` and `subscribe` (true unless said), `admin` and `hidden` (false unless said).
- Your server asks again when the callee answers, with the callee as `user`.

## Calling

```ts
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 given `ring: 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

```ts
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).

![Ann, Ben and Cara in one room from three browsers, with the call screen’s room window: each sees their own camera as You and the other two by name, then Mute, Turn camera off, Share screen and Leave. The cameras are illustrations](/img/apps/rooms-meeting.webp)

## The call screen

```ts
await rt.callScreen();   // the call window and the ring, on this page
```

```html
<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](react.md)).

![Ben has pressed a video call button: his browser shows the call window calling Ann, with his camera in a corner, and Ann’s browser rings with Accept and Decline](/img/apps/calls-ringing.webp)

![Ann has accepted: each browser shows the other person’s camera, their own in a corner, the call’s time, and Mute, Turn camera off, Share screen and Hang up. The cameras are illustrations](/img/apps/calls-connected.webp)

## 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](errors.md#calls)) |
| `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.
