# Room

## Televisit Room

## Configuring Room Triggers

There are three special trigger events for the room object: `room.after`, `participant.after` and `track.after`. Each occurs in response to a web hook call from the video provider. These events are also recorded in the room's `events[]` list.

Note `recording.after` and `composition.after` will be implemented in future versions.

To handle events triggered by the remote room provider, insert these Cortex script triggers.

```
org.objects.scripts.insertMany([{
  type: 'trigger',
  active: true,
  configuration: {
    event: 'room.after',
    object: 'room',
  },
  description: 'c_room_room_event',
  label: 'c_room_room_event',
  name: 'c_room_room_event',
  principal: null,
  script: `
    const { arguments: { event, old: room }, context } = script 
    require('logger').trace(event)
  `
},{
  type: 'trigger',
  active: true,
  configuration: {
    event: 'participant.after',
    object: 'room',
  },
  description: 'c_room_participant_event',
  label: 'c_room_participant_event',
  name: 'c_room_participant_event',
  principal: null,
  script: `
    const { arguments: { event, old: room }, context } = script 
    require('logger').trace(event)
  `
},{
  type: 'trigger',
  active: true,
  configuration: {
    event: 'track.after',
    object: 'room',
  },
  description: 'c_room_track_event',
  label: 'c_room_track_event',
  name: 'c_room_track_event',
  principal: null,
  script: `
    const { arguments: { event, old: room }, context } = script 
    require('logger').trace(event)
  `
}]).execute()
```

## Room Events

All room events share the following properties:

* `type` { String } Indexed room event type \[`room`, `participant`, `track`]
* `name` { String } Indexed room event name. `created`, `ended`, `connected`, etc.
* `order` { Number } Indexed integer representing the order in which the remote event was fired. It's possible that

  events are called and written out of order. The `order` property may be used to present the remote timeline.
* `roomId` { ObjectID } Indexed originating local room identifier.

### `room.after :: room.created` - Room is open and available for connection.

```
{
  type: 'room',
  name: 'created',
  order: 0,
  roomId: '5df02d890535377c38cfdeef'
}
```

### `room.after :: room.ended` - Room is closed, or ended due to a timeout (empty for 5 minutes).

* `duration` { Number } The number of seconds the room was active.

```
{
  type: 'room',
  name: 'ended',
  order: 12,
  roomId: '5df02d890535377c38cfdeef',
  duration: 156 
}
```

### `participant.after :: participant.connected` - Participant entered a room

* `status` { String } The current status of the participant generating this event - either `connected` or `disconnected`.
* `accountId` { ObjectID } The id of the account generating the event.

```
{
  type: 'participant',
  name: 'connected',
  order: 1,
  roomId: '5df02d890535377c38cfdeef',
  status: 'connected',
  accountId: '58c87b958b8f160100812b70'
}
```

### `participant.after :: participant.disconnected` - Participant left a room

* `status` { String } The current status of the participant generating this event - either `connected` or `disconnected`.
* `accountId` { ObjectID } The id of the account generating the event.
* `duration` { Number } The number of seconds the participant was connected to the room.

```
{
  type: 'participant',
  name: 'disconnected',
  order: 8,
  roomId: '5df02d890535377c38cfdeef',
  status: 'disconnected',
  accountId: '58c87b958b8f160100812b70',
  duration: 101 
}
```

### `track.after :: track.added` - Participant added a track

* `status` { String } The current status of the participant generating this event - either `connected` or `disconnected`.
* `accountId` { ObjectID } The id of the account generating the event.
* `trackKind` { String } The kind of track - `data`, `audio` or `video`.
* `trackName` { String } The track name. Currently unused and set to a uuid.

```
{
  type: 'track',
  name: 'added',
  order: 2,
  roomId: '5df02d890535377c38cfdeef',
  status: 'connected',
  accountId: '58c87b958b8f160100812b70',
  trackKind: 'audio',
  trackName: '613ef5df-cdee-4fd6-8f19-7140deb08b81'  
}
```

### `track.after :: track.removed` - Participant removed a track

* `status` { String } The current status of the participant generating this event - either `connected` or `disconnected`.
* `accountId` { ObjectID } The id of the account generating the event.
* `trackKind` { String } The kind of track - `data`, `audio` or `video`.
* `trackName` { String } The track name. Currently unused and set to a uuid.

```
{
  type: 'track',
  name: 'removed',
  order: 21,
  roomId: '5df02d890535377c38cfdeef',
  status: 'connected',
  accountId: '58c87b958b8f160100812b70',
  trackKind: 'video',
  trackName: '8bd80b33-34ab-4ac6-b2fd-23d51cfe9156'  

}
```

### `track.after :: track.enabled` - Participant un-paused a track

* `status` { String } The current status of the participant generating this event - either `connected` or `disconnected`.
* `accountId` { ObjectID } The id of the account generating the event.
* `trackKind` { String } The kind of track - `data`, `audio` or `video`.
* `trackName` { String } The track name. Currently unused and set to a uuid.

```
{
  type: 'track',
  name: 'enabled',
  order: 22,
  roomId: '5df02d890535377c38cfdeef',
  status: 'connected',
  accountId: '58c87b958b8f160100812b70',
  trackKind: 'video',
  trackName: '8bd80b33-34ab-4ac6-b2fd-23d51cfe9156'  

}
```

### `track.after :: track.disabled` - Participant paused a track

* `status` { String } The current status of the participant generating this event - either `connected` or `disconnected`.
* `accountId` { ObjectID } The id of the account generating the event.
* `trackKind` { String } The kind of track - `data`, `audio` or `video`.
* `trackName` { String } The track name. Currently unused and set to a uuid.

```
{
  type: 'track',
  name: 'enabled',
  order: 21,
  roomId: '5df02d890535377c38cfdeef',
  status: 'connected',
  accountId: '58c87b958b8f160100812b70',
  trackKind: 'video',
  trackName: '8bd80b33-34ab-4ac6-b2fd-23d51cfe9156'  

}
```

## Configuring Access

The Room definition `defaultAcl` is `owner.delete` and there is no `createAcl` defined for Rooms; Rooms must be created in-script using `bypassCreateAcl()`.

Alternatively, the Room definition may be extended with overriding `createAcl`, `defaultAcl` and - incidentally - custom properties.

```
org.objects.objects.insertOne({
  label: 'Room',
  name: 'room',
  createAcl: 'account.public', // any user can create rooms.
  properties: [{
    name: 'c_ustom',
    label: 'Custom',
    type: 'String'
  }]
})
```

Access to instances is provided using explicit `acl` writing. the shorthand import/export format can be used as well as the standard `type`, `target`, `allow` properties. Anyone with `read` access to the room instance is provided a token when reading the `token` property. For example:

```
org.objects.rooms.insertOne({  
  acl: [
    'account.public.read', // any logged in account
    'account.anonymous.read', // anyone anywhere
    'role.developer.read', // developer role
    `account.${emailToId('sample@medable.com')}.read`, // specific account (by id)           
    { type: 1, target: emailToId('sample@medable.com'), allow: 4 } // long form
  ]
})
.bypassCreateAcl()
.execute()

function emailToId(email) {
  return org.objects.accounts.readOne({ email }).skipAcl().grant('read').execute()._id
}
```

## Creating Rooms

Some environment-level options are required to create rooms.

* `org.configuration.televisit.roomsEnabled` must be true in order to create rooms in an environment.
* `org.configuration.televisit.maxConcurrentRooms` determines how many open rooms are allowed in a given environment.

To see current values for the above read current environment as an administrator (`return org.read('configuration.televisit')`)

To create a room instance, write an acl and insert a room instance:

```
return org.objects.rooms.insertOne({
  acl: [   
    `role.developer.read` // example.
  ]
}).lean(false).execute()
```

The Cortex room instance is now created and the `state` is `"new"`. The provider room to which clients connect is opened asynchronously. Once this process has completed, the room's `state` property will be set to `"open"` and fire any `room.after` script triggers (`{type: 'room', name: 'created', ...}`).

Participant may now use their tokens to connect to the remote room.

## Participant Status

Current participant status is updated in the Room instance to reflect the state of connected participants as they connect, disconnect, add and pause tracks, etc. Participant status is updated prior to script triggers so the latest is available in room event triggers.

Example participants list where both are connected and streaming audio and video.

```javascript
const doc = org.objects.room
  .readOne('5df2cb2a020fd46a6351e66b')
  .paths('participants.status', 'participants.audio', 'participants.video', 'participants.account.name')
  .execute()

let json = {
  "_id": "5df2cb2a020fd46a6351e66b",
  "object": "room",
  "participants": [
    {
      "_id": "5df2cb3d020fd46a6351e70b",
      "account": {
        "_id": "58c87b958b8f160100812b70",
        "name": {
          "additional": [],
          "first": "Jane",
          "last": "Doe"
        },
        "object": "account"       
      },
      "audio": [
        "connected"
      ],
      "status": "connected",
      "video": [
        "connected"
      ]
    },
    {
      "_id": "5df2cce5020fd46a6351ef86",
      "account": {
        "_id": "5da9ff0fff5c5eb553a557f1",
        "name": {
          "additional": [],
          "first": "John",
          "last": "Doe"
        },
        "object": "account"       
      },
      "audio": [
        "connected"
      ],
      "status": "connected",
      "video": [
        "connected"
      ]
    }
  ]
}
```

* The `status` of the participant is `connected` when they are connected to the room.
* The `audio` and `video` are arrays that contain `connected` and/or `paused`.
  * When either of these tracks are removed, `connected` will no longer appear in the array, but `paused` may linger and

    should be ignored.
* When a participant status is `disconnected`:
  * Ignore the values in`audio` and `video`.
  * The participant remains in the list.
