Skip to main content

Meeting Observer

Setup

The Meeting Observer provides a one-way WebSocket connection based on the Rails ActionCable protocol, with AnyCable as its successor.

You can connect to any active room that was started using your API key.

Preparations

The connection URL follows this format: https://api.eyeson.team/rt?room_id=YOUR_ROOM_ID

Example Observer URL
https://api.eyeson.team/rt?room_id=651b0c4c3ae7400ea35db31fs
note

Depending on your implementation, you may need to use the wss:// protocol instead of https://.

Channel subscription

To receive events, subscribe to the channel named RoomChannel.

note

The connection automatically closes when the meeting ends.

Authorization

An API key is required to connect. If you don't have one yet, see our guide on obtaining an API key.

You can include your API key in one of three ways:

  1. As an "Authorization" header
  2. As a WebSocket protocol parameter
  3. As a URL query parameter: api_key=YOUR_API_KEY

The method you choose depends on your implementation environment. For example:

  • Browser implementations can't set headers, so use the WebSocket protocol
  • If your library doesn't support protocol modification, use the query parameter
danger

Handle your API key securely! Be especially careful with logging, which can accidentally expose your key.

Examples

The observer is already integrated in the eyeson-node library. But you can find a more detailed version down below.

import Eyeson from 'eyeson-node';

const eyeson = new Eyeson({ apiKey: '< api-key >' }); // configure to use your api key

const meeting = await eyeson.join(username);

const connection = eyeson.observer.connect(meeting.roomId);
connection.on('disconnected', reason => {
console.log('Channel closed', reason);
});
connection.on('event', event => {
if (event.type === 'room_update') {
console.log(event.content);
}
else if (event.type === 'chat') {
console.log(event.content);
}
else if (event.type === 'participant_update') {
console.log(event.participant);
}
// and many more
});

Simplified example of usage with eyeson-node

Here's an example of how to implement the observer in Nodejs.

import WebSocket from 'ws';
import { createCable } from '@anycable/core';

let cable = null;

const connect = (room_id) => {
const url = `https://api.eyeson.team/rt?room_id=${room_id}`;
cable = createCable(url, {
websocketImplementation: WebSocket,
websocketOptions: {
headers: {
'Authorization': process.env.API_KEY,
},
},
});
const channel = cable.subscribeTo('RoomChannel');
channel.on('message', onMessage);
channel.on('connect', onConnected);
channel.on('disconnect', onDisconnected);
};

const disconnect = () => {
if (cable) {
cable.disconnect();
cable = null;
}
};

const onMessage = message => {
if (message.type === 'room_update') {
if (message.content.ready === true) {
console.log('Meeting ready');
}
if (message.content.shutdown === true) {
console.log('Meeting shutdown - disconnect');
disconnect();
}
}
};

const onConnected = () => {
console.log('Connected');
};

const onDisconnected = ({ name, message, reason }) => {
console.log('Disconnected', name, message, reason);
if (reason === 'unauthorized') {
disconnect();
}
};

Events

This is a list with examples of all events that can be observed during a meeting.

room_update

Received after connection is established, when the room is ready, or room shutdown changes to true.

{
"type": "room_update",
"content": {
"id": "6576daef1f138900153d0762",
"name": "Observer demo",
"ready": true,
"started_at": "2023-12-11T09:48:32.031Z",
"shutdown": false,
"guest_token": "jCWzhzCWONkbIubUYF7PD3hP",
"options": {
"show_names": true,
"show_label": true,
"exit_url": null,
"recording_available": true,
"broadcast_available": true,
"layout_available": true,
"layout": "auto",
"reaction_available": true,
"suggest_guest_names": true,
"lock_available": true,
"kick_available": true,
"sfu_mode": "ptp",
"layout_users": null,
"layout_name": null,
"layout_map": null,
"voice_activation": false,
"custom_fields": {},
"widescreen": true,
"background_color": "#121212"
},
"participants": [],
"playbacks": [],
"presentation": null,
"broadcasts": [],
"recording": null
}
}

You can already see all current participants, playbacks, broadcasts, and the recording's active state.

When the meeting shuts down, the connection is automatically closed.

participant_update

As soon as a participant joins or leaves the meeting, this event is triggered and you can determine join or leave by the online parameter.

{
"type": "participant_update",
"participant": {
"id": "test@observer.com",
"room_id": "6576daef1f138900153d0762",
"name": "Observer",
"avatar": null,
"guest": false,
"online": true
}
}

recording_update

Each recording has 3 states. Start, stop, and finish. So you will receive 3 events and you can distinguish them by the duration and links.download fields.

  1. At start, both are null
  2. At stop, duration is set, but download is null
  3. At finish, both are set
{
"type": "recording_update",
"recording": {
"id": "6576db593df56e00150ae3ce",
"created_at": 1702288218,
"duration": 5,
"links": {
"self": "https://api.eyeson.team/recordings/6576db593df56e00150ae3ce",
"download": "https://fs.eyeson.com/meetings/6576daf01f138900153d0763/6576db593df56e00150ae3ce.webm?X-Amz-Algorithm=..."
},
"user": {
"id": "test@observer.com",
"name": "Observer",
"avatar": null,
"guest": false,
"joined_at": "2023-12-11T09:48:34.433Z"
},
"room": {
"id": "6576daef1f138900153d0762",
"name": "Observer demo",
"ready": true,
"started_at": "2023-12-11T09:48:32.031Z",
"shutdown": false,
"guest_token": "jCWzhzCWONkbIubUYF7PD3hP"
}
}
}

broadcasts_update

When a live stream starts, the broadcasts array will be filled. When it ends, the broadcasts array is empty.

{
"type": "broadcasts_update",
"broadcasts": [
{
"id": "generic",
"platform": "generic",
"player_url": "",
"user": {
"id": "test@observer.com",
"name": "Observer",
"avatar": null,
"guest": false,
"joined_at": "2023-12-11T09:48:34.433Z"
},
"room": {
"id": "6576daef1f138900153d0762",
"name": "Observer demo",
"ready": true,
"started_at": "2023-12-11T09:48:32.031Z",
"shutdown": false,
"guest_token": "jCWzhzCWONkbIubUYF7PD3hP"
}
}
]
}

options_update

As soon as any of the options change, this event is triggered. But for now this is only usable for layout changes and its related fields

show_names, layout, layout_users, layout_name, layout_map, and voice_activation.

{
"type": "options_update",
"options": {
"show_names": true,
"show_label": true,
"exit_url": null,
"recording_available": true,
"broadcast_available": true,
"layout_available": true,
"layout": "auto",
"reaction_available": true,
"suggest_guest_names": true,
"lock_available": true,
"kick_available": true,
"sfu_mode": "ptp",
"layout_users": [
"",
"",
"",
"",
"",
"",
"",
"",
""
],
"layout_name": "nine",
"layout_map": null,
"voice_activation": false,
"custom_fields": {},
"widescreen": true,
"background_color": "#121212"
}
}

snapshot_update

You might already guessed it, whenever a snapshot has been created, you will find all information in this event. In future, the snapshots array might contain multiple objects if multiple snapshots have been created at the same time.

{
"type": "snapshot_update",
"snapshots": [
{
"id": "6576dd5ee71541001518fc11",
"name": "1702288734",
"links": {
"download": "https://fs.eyeson.com/meetings/6576daf01f138900153d0763/snapshots/1702288734.jpg?X-Amz-Algorithm=..."
},
"creator": {
"id": "test@observer.com",
"name": "Observer",
"avatar": null,
"guest": false,
"joined_at": "2023-12-11T09:48:34.433Z"
},
"created_at": "2023-12-11T09:58:54.498Z",
"room": {
"id": "6576daef1f138900153d0762",
"name": "Observer demo",
"ready": true,
"started_at": "2023-12-11T09:48:32.031Z",
"shutdown": false,
"guest_token": "jCWzhzCWONkbIubUYF7PD3hP"
}
}
]
}

chat and custom

Chat messages are messages with type "chat" sent over the API messages endpoint. You can also set any other type, like for example "custom".

{
"type": "chat",
"content": "Hello world!",
"cid": "6576daf01f138900153d0764",
"user_id": "test@observer.com",
"created_at": "2023-12-11T09:59:33.892Z"
}

{
"type": "custom",
"content": "Your custom message",
"cid": "6576daf01f138900153d0764",
"user_id": "test@observer.com",
"created_at": "2023-12-11T10:01:49.353Z"
}

playback_update

The playing parameter is a list of all currently active playbacks. If one playback ends, a playback_update event is received where the playing list does not contain the ended playback anymore. When the list is empty, all playbacks have ended.

{
"type": "playback_update",
"playing": [
{
"play_id": "demo-video",
"url": "https://myawesomeapp.com/videos/demo-video.webm",
"name": null,
"audio": true,
"loop_count": 0,
"replacement_id": null
}
]
}

podium_update

Here you can see the exact state of the visible podium. How it is segmented and for each segment either the id of the participant or the play_id of a playback.

note

This event includes layers! The list is empty if no layers are set.

info

Whenever the podium changes, you will receive 2 events! The first with the previous and the second with the current state.

{
"type": "podium_update",
"podium": [
{
"user_id": "test@observer.com",
"play_id": null,
"width": 427,
"height": 240,
"left": 0,
"top": 0,
"z-index": 0
},
{
"user_id": null,
"play_id": null,
"width": 427,
"height": 240,
"left": 427,
"top": 0,
"z-index": 1
},
{
"user_id": null,
"play_id": null,
"width": 426,
"height": 240,
"left": 854,
"top": 0,
"z-index": 2
},
{
"user_id": null,
"play_id": null,
"width": 427,
"height": 240,
"left": 0,
"top": 240,
"z-index": 3
},
{
"user_id": null,
"play_id": null,
"width": 427,
"height": 240,
"left": 427,
"top": 240,
"z-index": 4
},
{
"user_id": null,
"play_id": null,
"width": 426,
"height": 240,
"left": 854,
"top": 240,
"z-index": 5
},
{
"user_id": null,
"play_id": null,
"width": 427,
"height": 240,
"left": 0,
"top": 480,
"z-index": 6
},
{
"user_id": null,
"play_id": null,
"width": 427,
"height": 240,
"left": 427,
"top": 480,
"z-index": 7
},
{
"user_id": null,
"play_id": null,
"width": 426,
"height": 240,
"left": 854,
"top": 480,
"z-index": 8
}
],
"layers": [
{
"z-index": 1,
"id": "da9c96e70034e45d1ba2b80a304c0808"
},
{
"z-index": -1,
"id": "fc26523a13be45fcf1728a5f7360e611"
}
]
}

lock

When the meeting room gets locked, you will receive this information.

{
"type": "lock",
"locked": true
}

presentation_update

A presentation is indicated by the presentation parameter. The user is the one who started the presentation. It is set to null if the presentation has stopped.

{
"type": "presentation_update",
"presentation": {
"room": {
"id": "6576daef1f138900153d0762",
"name": "Observer demo",
"ready": true,
"started_at": "2023-12-11T09:48:32.031Z",
"shutdown": false,
"guest_token": "jCWzhzCWONkbIubUYF7PD3hP"
},
"user": {
"id": "test@observer.com",
"name": "Observer",
"avatar": null,
"guest": false,
"joined_at": "2023-12-11T09:48:34.433Z"
}
}
}

Errors

In the following cases, the websocket connection can't be established.

Invalid or missing API key

The WebSocket will close the connection with the following message:

{ "type": "disconnect", "reason": "unauthorized", "reconnect": false }

Invalid or missing "room_id"

The WebSocket will close the connection with the following message:

{ "type": "disconnect", "reason": "unauthorized", "reconnect": false }