Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ 5e9e29e0

History | View | Annotate | Download (44.6 KB)

1 be35facb meetecho
/*! \file   janus_videoroom.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU Affero General Public License v3
4
 * \brief  Janus VideoRoom plugin
5
 * \details  This is a plugin implementing a videoconferencing MCU for Janus.
6
 * This means that the plugin implements a virtual conferencing room peers
7
 * can join and leave at any time. This room is based on a Publish/Subscribe
8
 * pattern. Each peer can publish his/her own live audio/video feeds: this
9
 * feed becomes an available stream in the room the other participants can
10
 * attach to. This means that this plugin allows the realization of several
11
 * different scenarios, ranging from a simple webinar (one speaker, several
12
 * listeners) to a fully meshed video conference (each peer sending and
13
 * receiving to and from all the others).
14
 * 
15
 * Considering that this plugin allows for several different WebRTC PeerConnections
16
 * to be on at the same time for the same peer (specifically, each peer
17
 * potentially has 1 PeerConnection on for publishing and N on for subscriptions
18
 * from other peers), each peer may need to attach several times to the same
19
 * plugin for every stream: this means that each peer needs to have at least one
20
 * handle active for managing its relation with the plugin (joining a room,
21
 * leaving a room, muting/unmuting, publishing, receiving events), and needs
22
 * to open a new one each time he/she wants to subscribe to a feed from
23
 * another participant. The handle used for a subscription, however, would
24
 * be logically a "slave" to the master one used for managing the room: this
25
 * means that it cannot be used, for instance, to unmute in the room, as its
26
 * only purpose would be to provide a context in which creating the sendonly
27
 * PeerConnection for the subscription to the active participant.
28
 * 
29
 * Rooms to make available are listed in the plugin configuration file.
30
 * A pre-filled configuration file is provided in \c conf/janus.plugin.videoroom.cfg
31
 * and includes a demo room for testing.
32
 * 
33
 * To add more rooms or modify the existing one, you can use the following
34
 * syntax:
35
 * 
36
 * \verbatim
37
[<unique room ID>]
38
description = This is my awesome room
39
publishers = <max number of concurrent senders> (e.g., 6 for a video
40
             conference or 1 for a webinar)
41
bitrate = <max video bitrate for senders> (e.g., 128000)
42 5e9e29e0 meetecho
fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
43 be35facb meetecho
\endverbatim
44
 *
45
 * \ingroup plugins
46
 * \ref plugins
47
 */
48
49
#include "plugin.h"
50
51
#include <jansson.h>
52
53
#include "../config.h"
54
#include "../rtcp.h"
55 5e9e29e0 meetecho
#include "../utils.h"
56 be35facb meetecho
57
58
/* Plugin information */
59
#define JANUS_VIDEOROOM_VERSION                        1
60
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.1"
61
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing MCU for Janus, something like Licode."
62
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
63
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
64
65
/* Plugin methods */
66
janus_plugin *create(void);
67
int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
68
void janus_videoroom_destroy(void);
69
int janus_videoroom_get_version(void);
70
const char *janus_videoroom_get_version_string(void);
71
const char *janus_videoroom_get_description(void);
72
const char *janus_videoroom_get_name(void);
73
const char *janus_videoroom_get_package(void);
74
void janus_videoroom_create_session(janus_pluginession *handle, int *error);
75
void janus_videoroom_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp);
76
void janus_videoroom_setup_media(janus_pluginession *handle);
77
void janus_videoroom_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len);
78
void janus_videoroom_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len);
79
void janus_videoroom_hangup_media(janus_pluginession *handle);
80
void janus_videoroom_destroy_session(janus_pluginession *handle, int *error);
81
82
/* Plugin setup */
83
static janus_plugin janus_videoroom_plugin =
84
        {
85
                .init = janus_videoroom_init,
86
                .destroy = janus_videoroom_destroy,
87
88
                .get_version = janus_videoroom_get_version,
89
                .get_version_string = janus_videoroom_get_version_string,
90
                .get_description = janus_videoroom_get_description,
91
                .get_name = janus_videoroom_get_name,
92
                .get_package = janus_videoroom_get_package,
93
                
94
                .create_session = janus_videoroom_create_session,
95
                .handle_message = janus_videoroom_handle_message,
96
                .setup_media = janus_videoroom_setup_media,
97
                .incoming_rtp = janus_videoroom_incoming_rtp,
98
                .incoming_rtcp = janus_videoroom_incoming_rtcp,
99
                .hangup_media = janus_videoroom_hangup_media,
100
                .destroy_session = janus_videoroom_destroy_session,
101
        }; 
102
103
/* Plugin creator */
104
janus_plugin *create(void) {
105
        JANUS_PRINT("%s created!\n", JANUS_VIDEOROOM_NAME);
106
        return &janus_videoroom_plugin;
107
}
108
109
110
/* Useful stuff */
111
static int initialized = 0, stopping = 0;
112
static janus_callbacks *gateway = NULL;
113
static GThread *handler_thread;
114
static void *janus_videoroom_handler(void *data);
115
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
116
char *string_replace(char *message, char *old, char *new, int *modified);
117
118
typedef enum janus_videoroom_p_type {
119
        janus_videoroom_p_type_none = 0,
120
        janus_videoroom_p_type_subscriber,
121
        janus_videoroom_p_type_publisher,
122
} janus_videoroom_p_type;
123
124
typedef struct janus_videoroom_message {
125
        janus_pluginession *handle;
126
        char *transaction;
127
        char *message;
128
        char *sdp_type;
129
        char *sdp;
130
} janus_videoroom_message;
131
GQueue *messages;
132
133
typedef struct janus_videoroom {
134
        guint64 room_id;        /* Unique room ID */
135
        gchar *room_name;        /* Room description */
136
        int max_publishers;        /* Maximum number of concurrent publishers */
137
        uint64_t bitrate;        /* Global bitrate limit */
138 5e9e29e0 meetecho
        uint16_t fir_freq;        /* Regular FIR frequency (0=disabled) */
139 be35facb meetecho
        gboolean destroy;
140
        GHashTable *participants;        /* Map of potential publishers (we get listeners from them) */
141
} janus_videoroom;
142
GHashTable *rooms;
143
144
typedef struct janus_videoroom_session {
145
        janus_pluginession *handle;
146
        janus_videoroom_p_type participant_type;
147
        gpointer participant;
148
        gboolean started;
149
        gboolean stopping;
150
        gboolean destroy;
151
} janus_videoroom_session;
152
GHashTable *sessions;
153
154
typedef struct janus_videoroom_participant {
155
        janus_videoroom_session *session;
156
        janus_videoroom *room;        /* Room */
157
        guint64 user_id;        /* Unique ID in the room */
158
        gchar *display;        /* Display name (just for fun) */
159
        gchar *sdp;                        /* The SDP this publisher negotiated, if any */
160
        gboolean audio_active;
161
        gboolean video_active;
162 5e9e29e0 meetecho
        gboolean firefox;        /* We send Firefox users a different kind of FIR */
163 be35facb meetecho
        uint64_t bitrate;
164
        gint64 fir_latest;        /* Time of latest sent FIR (to avoid flooding) */
165
        gint fir_seq;                /* FIR sequence number */
166
        GSList *listeners;
167
} janus_videoroom_participant;
168
169
typedef struct janus_videoroom_listener {
170
        janus_videoroom_session *session;
171
        janus_videoroom *room;        /* Room */
172
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
173
        gboolean paused;
174
} janus_videoroom_listener;
175
176
typedef struct janus_videoroom_rtp_relay_packet {
177
        char *data;
178
        gint length;
179
        gint is_video;
180
} janus_videoroom_rtp_relay_packet;
181
182
183
/* Plugin implementation */
184
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
185
        if(stopping) {
186
                /* Still stopping from before */
187
                return -1;
188
        }
189
        if(callback == NULL || config_path == NULL) {
190
                /* Invalid arguments */
191
                return -1;
192
        }
193
194
        /* Read configuration */
195
        char filename[255];
196
        sprintf(filename, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
197
        JANUS_PRINT("Configuration file: %s\n", filename);
198
        janus_config *config = janus_config_parse(filename);
199
        if(config != NULL)
200
                janus_config_print(config);
201
202
        rooms = g_hash_table_new(NULL, NULL);
203
        sessions = g_hash_table_new(NULL, NULL);
204
        messages = g_queue_new();
205
        /* This is the callback we'll need to invoke to contact the gateway */
206
        gateway = callback;
207
208
        /* Parse configuration to populate the rooms list */
209
        if(config != NULL) {
210
                janus_config_category *cat = janus_config_get_categories(config);
211
                while(cat != NULL) {
212
                        if(cat->name == NULL) {
213
                                cat = cat->next;
214
                                continue;
215
                        }
216
                        JANUS_PRINT("Adding video room '%s'\n", cat->name);
217
                        janus_config_item *desc = janus_config_get_item(cat, "description");
218
                        janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
219
                        janus_config_item *maxp = janus_config_get_item(cat, "publishers");
220 5e9e29e0 meetecho
                        janus_config_item *firfreq = janus_config_get_item(cat, "fir_freq");
221 be35facb meetecho
                        /* Create the video mcu room */
222
                        janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
223
                        if(videoroom == NULL) {
224
                                JANUS_DEBUG("Memory error!\n");
225
                                continue;
226
                        }
227
                        videoroom->room_id = atoi(cat->name);
228
                        char *description = NULL;
229
                        if(desc != NULL && desc->value != NULL)
230
                                description = g_strdup(desc->value);
231
                        else
232
                                description = g_strdup(cat->name);
233
                        if(description == NULL) {
234
                                JANUS_DEBUG("Memory error!\n");
235
                                continue;
236
                        }
237
                        videoroom->room_name = description;
238
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
239
                        if(maxp != NULL && maxp->value != NULL)
240
                                videoroom->max_publishers = atol(maxp->value);
241
                        if(videoroom->max_publishers < 0)
242
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
243
                        videoroom->bitrate = 0;
244
                        if(bitrate != NULL && bitrate->value != NULL)
245
                                videoroom->bitrate = atol(bitrate->value);
246 5e9e29e0 meetecho
                        videoroom->fir_freq = 0;
247
                        if(firfreq != NULL && firfreq->value != NULL)
248
                                videoroom->fir_freq = atol(firfreq->value);
249 be35facb meetecho
                        videoroom->destroy = 0;
250
                        videoroom->participants = g_hash_table_new(NULL, NULL);
251
                        g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
252
                        JANUS_PRINT("Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
253
                        cat = cat->next;
254
                }
255
                /* Done */
256
                janus_config_destroy(config);
257
                config = NULL;
258
        }
259
260
        /* Show available rooms */
261
        GList *rooms_list = g_hash_table_get_values(rooms);
262
        GList *r = rooms_list;
263
        while(r) {
264
                janus_videoroom *vr = (janus_videoroom *)r->data;
265 5e9e29e0 meetecho
                JANUS_PRINT("  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
266 be35facb meetecho
                r = r->next;
267
        }
268
        g_list_free(rooms_list);
269
270
        initialized = 1;
271
        /* Launch the thread that will handle incoming messages */
272
        GError *error = NULL;
273
        handler_thread = g_thread_try_new("janus videoroom handler", janus_videoroom_handler, NULL, &error);
274
        if(error != NULL) {
275
                initialized = 0;
276
                /* Something went wrong... */
277
                JANUS_DEBUG("Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??");
278
                return -1;
279
        }
280
        JANUS_PRINT("%s initialized!\n", JANUS_VIDEOROOM_NAME);
281
        return 0;
282
}
283
284
void janus_videoroom_destroy() {
285
        if(!initialized)
286
                return;
287
        stopping = 1;
288
        if(handler_thread != NULL) {
289
                g_thread_join(handler_thread);
290
        }
291
        handler_thread = NULL;
292
        /* TODO Actually remove rooms and its participants */
293
        g_hash_table_destroy(sessions);
294
        g_hash_table_destroy(rooms);
295
        g_queue_free(messages);
296
        rooms = NULL;
297
        initialized = 0;
298
        stopping = 0;
299
        JANUS_PRINT("%s destroyed!\n", JANUS_VIDEOROOM_NAME);
300
}
301
302
int janus_videoroom_get_version() {
303
        return JANUS_VIDEOROOM_VERSION;
304
}
305
306
const char *janus_videoroom_get_version_string() {
307
        return JANUS_VIDEOROOM_VERSION_STRING;
308
}
309
310
const char *janus_videoroom_get_description() {
311
        return JANUS_VIDEOROOM_DESCRIPTION;
312
}
313
314
const char *janus_videoroom_get_name() {
315
        return JANUS_VIDEOROOM_NAME;
316
}
317
318
const char *janus_videoroom_get_package() {
319
        return JANUS_VIDEOROOM_PACKAGE;
320
}
321
322
void janus_videoroom_create_session(janus_pluginession *handle, int *error) {
323
        if(stopping || !initialized) {
324
                *error = -1;
325
                return;
326
        }        
327
        janus_videoroom_session *session = (janus_videoroom_session *)calloc(1, sizeof(janus_videoroom_session));
328
        if(session == NULL) {
329
                JANUS_DEBUG("Memory error!\n");
330
                *error = -2;
331
                return;
332
        }
333
        session->handle = handle;
334
        session->participant_type = janus_videoroom_p_type_none;
335
        session->participant = NULL;
336
        handle->plugin_handle = session;
337
        g_hash_table_insert(sessions, handle, session);
338
339
        return;
340
}
341
342
void janus_videoroom_destroy_session(janus_pluginession *handle, int *error) {
343
        if(stopping || !initialized) {
344
                *error = -1;
345
                return;
346
        }        
347
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle; 
348
        if(!session) {
349
                JANUS_DEBUG("No session associated with this handle...\n");
350
                *error = -2;
351
                return;
352
        }
353
        if(session->destroy) {
354
                JANUS_PRINT("Session already destroyed...\n");
355
                g_free(session);
356
                return;
357
        }
358
        JANUS_PRINT("Removing Video Room session...\n");
359
        /* TODO Actually clean up session, e.g., removing listener from publisher and viceversa */
360
        g_hash_table_remove(sessions, handle);
361
        if(session->participant_type == janus_videoroom_p_type_publisher) {
362
                /* TODO Get rid of this publisher and its listeners */
363
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
364
                /* TODO Detach this listener from its subscriber */
365
        }
366
        janus_videoroom_hangup_media(handle);
367
        session->destroy = TRUE;
368
        g_free(session);
369
370
        return;
371
}
372
373
void janus_videoroom_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
374
        if(stopping || !initialized)
375
                return;
376
        JANUS_PRINT("%s\n", message);
377
        janus_videoroom_message *msg = calloc(1, sizeof(janus_videoroom_message));
378
        if(msg == NULL) {
379
                JANUS_DEBUG("Memory error!\n");
380
                return;
381
        }
382
        msg->handle = handle;
383
        msg->transaction = transaction ? g_strdup(transaction) : NULL;
384
        msg->message = message;
385
        msg->sdp_type = sdp_type;
386
        msg->sdp = sdp;
387
        g_queue_push_tail(messages, msg);
388
}
389
390
void janus_videoroom_setup_media(janus_pluginession *handle) {
391
        JANUS_DEBUG("WebRTC media is now available\n");
392
        if(stopping || !initialized)
393
                return;
394
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
395
        if(!session) {
396
                JANUS_DEBUG("No session associated with this handle...\n");
397
                return;
398
        }
399
        if(session->destroy)
400
                return;
401
        /* Media relaying can start now */
402
        session->started = TRUE;
403
        /* If this is a listener, ask the publisher a FIR */
404
        if(session->participant && session->participant_type == janus_videoroom_p_type_subscriber) {
405
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
406
                if(l && l->feed) {
407
                        janus_videoroom_participant *p = l->feed;
408
                        if(p && p->session) {
409
                                /* Send a FIR */
410
                                char buf[20];
411
                                memset(buf, 0, 20);
412 5e9e29e0 meetecho
                                if(!p->firefox)
413
                                        janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
414
                                else
415
                                        janus_rtcp_fir_legacy((char *)&buf, 20, &p->fir_seq);
416 be35facb meetecho
                                JANUS_PRINT("New listener available, sending FIR to %s\n", p->display);
417
                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
418
                                /* Send a PLI too, just in case... */
419
                                memset(buf, 0, 12);
420
                                janus_rtcp_pli((char *)&buf, 12);
421
                                JANUS_PRINT("New listener available, sending PLI to %s\n", p->display);
422
                                gateway->relay_rtcp(p->session->handle, 1, buf, 12);
423
                        }
424
                }
425
        }
426
}
427
428
void janus_videoroom_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len) {
429
        if(stopping || !initialized || !gateway)
430
                return;
431
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
432
        if(!session || session->destroy || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
433
                return;
434
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
435
        if((!video && participant->audio_active) || (video && participant->video_active)) {
436
                janus_videoroom_rtp_relay_packet packet;
437
                packet.data = buf;
438
                packet.length = len;
439
                packet.is_video = video;
440
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
441 5e9e29e0 meetecho
                if(video && (participant->room->fir_freq > 0)) {
442 be35facb meetecho
                        /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
443 5e9e29e0 meetecho
                        gint64 now = janus_get_monotonic_time();
444
                        if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
445
                                /* FIXME We send a FIR every tot seconds */
446 be35facb meetecho
                                participant->fir_latest = now;
447
                                char buf[20];
448
                                memset(buf, 0, 20);
449 5e9e29e0 meetecho
                                if(!participant->firefox)
450
                                        janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
451
                                else
452
                                        janus_rtcp_fir_legacy((char *)&buf, 20, &participant->fir_seq);
453 be35facb meetecho
                                JANUS_PRINT("Sending FIR to %s\n", participant->display);
454
                                gateway->relay_rtcp(handle, video, buf, 20);
455
                                /* Send a PLI too, just in case... */
456
                                memset(buf, 0, 12);
457
                                janus_rtcp_pli((char *)&buf, 12);
458 5e9e29e0 meetecho
                                JANUS_PRINT("Sending PLI to %s\n", participant->display);
459 be35facb meetecho
                                gateway->relay_rtcp(handle, video, buf, 12);
460
                        }
461
                }
462
        }
463
}
464
465
void janus_videoroom_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len) {
466
        if(stopping || !initialized || !gateway)
467
                return;
468
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
469
        if(!session || session->destroy || !session->participant || !video)
470
                return;
471
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
472
                /* FIXME Badly: we're blinding forwarding the listener RTCP t the publisher: this probably means confusing him... */
473
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
474
                if(l && l->feed) {
475
                        janus_videoroom_participant *p = l->feed;
476
                        if(p && p->session) {
477
                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
478
                        }
479
                }
480
        } else if(session->participant_type == janus_videoroom_p_type_publisher) {
481
                /* FIXME Badly: we're just bouncing the incoming RTCP back with modified REMB, we need to improve this... */
482
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
483
                if(participant->bitrate > 0)
484
                        janus_rtcp_cap_remb(buf, len, participant->bitrate);
485
                gateway->relay_rtcp(handle, video, buf, len);
486
        }
487
}
488
489
void janus_videoroom_hangup_media(janus_pluginession *handle) {
490
        JANUS_PRINT("No WebRTC media anymore\n");
491
        if(stopping || !initialized)
492
                return;
493
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
494
        if(!session) {
495
                JANUS_DEBUG("No session associated with this handle...\n");
496
                return;
497
        }
498
        if(session->destroy)
499
                return;
500
        /* Send an event to the browser and tell it's over */
501
        if(session->participant_type == janus_videoroom_p_type_publisher) {
502
                /* Get rid of publisher */
503
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
504
                json_t *event = json_object();
505
                json_object_set(event, "videoroom", json_string("event"));
506
                json_object_set(event, "room", json_integer(participant->room->room_id));
507
                json_object_set(event, "leaving", json_integer(participant->user_id));
508
                char *leaving_text = json_dumps(event, JSON_INDENT(3));
509
                json_decref(event);
510
                g_hash_table_remove(participant->room->participants, GUINT_TO_POINTER(participant->user_id));
511
                GList *participants_list = g_hash_table_get_values(participant->room->participants);
512
                GList *ps = participants_list;
513
                while(ps) {
514
                        janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
515
                        if(p == participant) {
516
                                ps = ps->next;
517
                                continue;        /* Skip the leaving publisher itself */
518
                        }
519
                        JANUS_PRINT("Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
520
                        JANUS_PRINT("  >> %d\n", gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL));
521
                        ps = ps->next;
522
                }
523
                g_free(leaving_text);
524
                g_list_free(participants_list);
525
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
526
                /* Get rid of listener */
527
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
528
                janus_videoroom_participant *publisher = listener->feed;
529
                if(publisher != NULL) {
530
                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
531
                        listener->feed = NULL;
532
                }
533
        }
534
}
535
536
/* Thread to handle incoming messages */
537
static void *janus_videoroom_handler(void *data) {
538
        JANUS_DEBUG("Joining thread\n");
539
        janus_videoroom_message *msg = NULL;
540
        char *error_cause = calloc(512, sizeof(char));        /* FIXME 512 should be enough, but anyway... */
541
        if(error_cause == NULL) {
542
                JANUS_DEBUG("Memory error!\n");
543
                return NULL;
544
        }
545
        while(initialized && !stopping) {
546
                if(!messages || (msg = g_queue_pop_head(messages)) == NULL) {
547
                        usleep(50000);
548
                        continue;
549
                }
550
                janus_videoroom_session *session = (janus_videoroom_session *)msg->handle->plugin_handle;        
551
                if(!session) {
552
                        JANUS_DEBUG("No session associated with this handle...\n");
553
                        continue;
554
                }
555
                if(session->destroy)
556
                        continue;
557
                /* Handle request */
558
                JANUS_PRINT("Handling message: %s\n", msg->message);
559
                if(msg->message == NULL) {
560
                        JANUS_DEBUG("No message??\n");
561
                        sprintf(error_cause, "%s", "No message??");
562
                        goto error;
563
                }
564
                json_error_t error;
565
                json_t *root = json_loads(msg->message, 0, &error);
566
                if(!root) {
567
                        JANUS_DEBUG("JSON error: on line %d: %s\n", error.line, error.text);
568
                        sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text);
569
                        goto error;
570
                }
571
                if(!json_is_object(root)) {
572
                        JANUS_DEBUG("JSON error: not an object\n");
573
                        sprintf(error_cause, "JSON error: not an object");
574
                        goto error;
575
                }
576
                /* Get the request first */
577
                json_t *request = json_object_get(root, "request");
578
                if(!request || !json_is_string(request)) {
579
                        JANUS_DEBUG("JSON error: invalid element (request)\n");
580
                        sprintf(error_cause, "JSON error: invalid element (request)");
581
                        goto error;
582
                }
583
                const char *request_text = json_string_value(request);
584
                json_t *event = NULL;
585 47576630 meetecho
                if(!strcasecmp(request_text, "create")) {
586
                        /* Create a new videoroom */
587
                        JANUS_PRINT("Creating a new videoroom\n");
588
                        json_t *desc = json_object_get(root, "description");
589
                        if(desc && !json_is_string(desc)) {
590
                                JANUS_DEBUG("JSON error: invalid element (desc)\n");
591
                                sprintf(error_cause, "JSON error: invalid element (desc)");
592
                                goto error;
593
                        }
594
                        json_t *bitrate = json_object_get(root, "bitrate");
595
                        if(bitrate && !json_is_integer(bitrate)) {
596
                                JANUS_DEBUG("JSON error: invalid element (bitrate)\n");
597
                                sprintf(error_cause, "JSON error: invalid element (bitrate)");
598
                                goto error;
599
                        }
600 5e9e29e0 meetecho
                        json_t *fir_freq = json_object_get(root, "fir_freq");
601
                        if(fir_freq && !json_is_integer(fir_freq)) {
602
                                JANUS_DEBUG("JSON error: invalid element (fir_freq)\n");
603
                                sprintf(error_cause, "JSON error: invalid element (fir_freq)");
604
                                goto error;
605
                        }
606 47576630 meetecho
                        json_t *publishers = json_object_get(root, "publishers");
607
                        if(publishers && !json_is_integer(publishers)) {
608
                                JANUS_DEBUG("JSON error: invalid element (publishers)\n");
609
                                sprintf(error_cause, "JSON error: invalid element (publishers)");
610
                                goto error;
611
                        }
612
                        /* Create the audio bridge room */
613
                        janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
614
                        if(videoroom == NULL) {
615
                                JANUS_DEBUG("Memory error!\n");
616
                                sprintf(error_cause, "Memory error");
617
                                goto error;
618
                        }
619
                        /* Generate a random ID */
620
                        guint64 room_id = 0;
621
                        while(room_id == 0) {
622
                                room_id = g_random_int();
623
                                if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
624
                                        /* Room ID already taken, try another one */
625
                                        room_id = 0;
626
                                }
627
                        }
628
                        videoroom->room_id = room_id;
629
                        char *description = NULL;
630
                        if(desc != NULL) {
631
                                description = g_strdup(json_string_value(desc));
632
                        } else {
633
                                char roomname[255];
634
                                sprintf(roomname, "Room %"SCNu64"", videoroom->room_id);
635
                                description = g_strdup(roomname);
636
                        }
637
                        if(description == NULL) {
638
                                JANUS_DEBUG("Memory error!\n");
639
                                continue;
640
                        }
641
                        videoroom->room_name = description;
642
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
643
                        if(publishers)
644
                                videoroom->max_publishers = json_integer_value(publishers);
645
                        if(videoroom->max_publishers < 0)
646
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
647
                        videoroom->bitrate = 0;
648
                        if(bitrate)
649
                                videoroom->bitrate = json_integer_value(bitrate);
650 5e9e29e0 meetecho
                        videoroom->fir_freq = 0;
651
                        if(fir_freq)
652
                                videoroom->fir_freq = json_integer_value(fir_freq);
653 47576630 meetecho
                        videoroom->destroy = 0;
654
                        videoroom->participants = g_hash_table_new(NULL, NULL);
655
                        g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
656
                        JANUS_PRINT("Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
657
                        /* Show updated rooms list */
658
                        GList *rooms_list = g_hash_table_get_values(rooms);
659
                        GList *r = rooms_list;
660
                        while(r) {
661
                                janus_videoroom *vr = (janus_videoroom *)r->data;
662 5e9e29e0 meetecho
                                JANUS_PRINT("  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
663 47576630 meetecho
                                r = r->next;
664
                        }
665
                        g_list_free(rooms_list);
666
                        /* Send info back */
667
                        event = json_object();
668
                        json_object_set(event, "videoroom", json_string("created"));
669
                        json_object_set(event, "room", json_integer(videoroom->room_id));
670
                } else
671 be35facb meetecho
                /* What kind of participant is this session referring to? */
672
                if(session->participant_type == janus_videoroom_p_type_none) {
673
                        JANUS_PRINT("Configuring new participant\n");
674
                        /* Not configured yet, we need to do this now */
675
                        if(strcasecmp(request_text, "join")) {
676
                                JANUS_DEBUG("Invalid request on unconfigured participant\n");
677
                                sprintf(error_cause, "Invalid request on unconfigured participant");
678
                                goto error;
679
                        }
680
                        json_t *room = json_object_get(root, "room");
681
                        if(!room || !json_is_integer(room)) {
682
                                JANUS_DEBUG("JSON error: invalid element (room)\n");
683
                                sprintf(error_cause, "JSON error: invalid element (room)");
684
                                goto error;
685
                        }
686
                        guint64 room_id = json_integer_value(room);
687
                        janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
688
                        if(videoroom == NULL) {
689
                                JANUS_DEBUG("No such room (%"SCNu64")\n", room_id);
690
                                sprintf(error_cause, "No such room (%"SCNu64")", room_id);
691
                                goto error;
692
                        }
693
                        json_t *ptype = json_object_get(root, "ptype");
694
                        if(!ptype || !json_is_string(ptype)) {
695
                                JANUS_DEBUG("JSON error: invalid element (ptype)\n");
696
                                sprintf(error_cause, "JSON error: invalid element (ptype)");
697
                                goto error;
698
                        }
699
                        const char *ptype_text = json_string_value(ptype);
700
                        if(!strcasecmp(ptype_text, "publisher")) {
701
                                JANUS_PRINT("Configuring new publisher\n");
702
                                /* This is a new publisher: is there room? */
703
                                GList *participants_list = g_hash_table_get_values(videoroom->participants);
704
                                if(g_list_length(participants_list) == videoroom->max_publishers) {
705
                                        JANUS_DEBUG("Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
706
                                        sprintf(error_cause, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
707
                                        g_list_free(participants_list);
708
                                        goto error;
709
                                }
710
                                g_list_free(participants_list);
711
                                json_t *display = json_object_get(root, "display");
712
                                if(!display || !json_is_string(display)) {
713
                                        JANUS_DEBUG("JSON error: invalid element (display)\n");
714
                                        sprintf(error_cause, "JSON error: invalid element (display)");
715
                                        goto error;
716
                                }
717
                                const char *display_text = json_string_value(display);
718
                                /* Generate a random ID */
719
                                guint64 user_id = 0;
720
                                while(user_id == 0) {
721
                                        user_id = g_random_int();
722
                                        if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
723
                                                /* User ID already taken, try another one */
724
                                                user_id = 0;
725
                                        }
726
                                }
727
                                JANUS_PRINT("  -- Publisher ID: %"SCNu64"\n", user_id);
728
                                janus_videoroom_participant *publisher = calloc(1, sizeof(janus_videoroom_participant));
729
                                if(publisher == NULL) {
730
                                        JANUS_DEBUG("Memory error!\n");
731
                                        sprintf(error_cause, "Memory error");
732
                                        goto error;
733
                                }
734
                                publisher->session = session;
735
                                publisher->room = videoroom;
736
                                publisher->user_id = user_id;
737
                                publisher->display = g_strdup(display_text);
738
                                if(publisher->display == NULL) {
739
                                        JANUS_DEBUG("Memory error!\n");
740
                                        sprintf(error_cause, "Memory error");
741
                                        g_free(publisher);
742
                                        goto error;
743
                                }
744
                                publisher->sdp = NULL;        /* We'll deal with this later */
745
                                publisher->audio_active = FALSE;
746
                                publisher->video_active = FALSE;
747 5e9e29e0 meetecho
                                publisher->firefox = FALSE;
748 be35facb meetecho
                                publisher->bitrate = videoroom->bitrate;
749
                                publisher->listeners = NULL;
750
                                publisher->fir_latest = 0;
751
                                publisher->fir_seq = 0;
752
                                /* Done */
753
                                session->participant_type = janus_videoroom_p_type_publisher;
754
                                session->participant = publisher;
755
                                g_hash_table_insert(videoroom->participants, GUINT_TO_POINTER(user_id), publisher);
756
                                /* Return a list of all available publishers (those with an SDP available, that is) */
757
                                json_t *list = json_array();
758
                                participants_list = g_hash_table_get_values(videoroom->participants);
759
                                GList *ps = participants_list;
760
                                while(ps) {
761
                                        janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
762
                                        if(p == publisher || !p->sdp) {
763
                                                ps = ps->next;
764
                                                continue;
765
                                        }
766
                                        json_t *pl = json_object();
767
                                        json_object_set_new(pl, "id", json_integer(p->user_id));
768
                                        json_object_set_new(pl, "display", json_string(p->display));
769
                                        json_array_append_new(list, pl);
770
                                        ps = ps->next;
771
                                }
772
                                event = json_object();
773
                                json_object_set(event, "videoroom", json_string("joined"));
774
                                json_object_set(event, "room", json_integer(videoroom->room_id));
775
                                json_object_set(event, "id", json_integer(user_id));
776
                                json_object_set_new(event, "publishers", list);
777
                                g_list_free(participants_list);
778
                        } else if(!strcasecmp(ptype_text, "listener")) {
779
                                JANUS_PRINT("Configuring new listener\n");
780
                                /* This is a new listener */
781
                                json_t *feed = json_object_get(root, "feed");
782
                                if(!feed || !json_is_integer(feed)) {
783
                                        JANUS_DEBUG("JSON error: invalid element (feed)\n");
784
                                        sprintf(error_cause, "JSON error: invalid element (feed)");
785
                                        goto error;
786
                                }
787
                                guint64 feed_id = json_integer_value(feed);
788
                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
789
                                if(publisher == NULL || publisher->sdp == NULL) {
790
                                        JANUS_DEBUG("No such feed (%"SCNu64")\n", feed_id);
791
                                        sprintf(error_cause, "No such feed (%"SCNu64")", feed_id);
792
                                        goto error;
793
                                } else {
794
                                        janus_videoroom_listener *listener = calloc(1, sizeof(janus_videoroom_listener));
795
                                        if(listener == NULL) {
796
                                                JANUS_DEBUG("Memory error!\n");
797
                                                sprintf(error_cause, "Memory error");
798
                                                goto error;
799
                                        }
800
                                        listener->session = session;
801
                                        listener->room = videoroom;
802
                                        listener->feed = publisher;
803
                                        listener->paused = TRUE;        /* We need an explicit start from the listener */
804
                                        session->participant = listener;
805
                                        publisher->listeners = g_slist_append(publisher->listeners, listener);
806
                                        event = json_object();
807
                                        json_object_set(event, "videoroom", json_string("attached"));
808
                                        json_object_set(event, "room", json_integer(videoroom->room_id));
809
                                        json_object_set(event, "id", json_integer(feed_id));
810
                                        json_object_set(event, "display", json_string(publisher->display));
811
                                        session->participant_type = janus_videoroom_p_type_subscriber;
812
                                        JANUS_PRINT("Preparing JSON event as a reply\n");
813
                                        char *event_text = json_dumps(event, JSON_INDENT(3));
814
                                        json_decref(event);
815
                                        /* Negotiate by sending the selected publisher SDP back */
816
                                        if(publisher->sdp != NULL) {
817
                                                /* How long will the gateway take to push the event? */
818 5e9e29e0 meetecho
                                                gint64 start = janus_get_monotonic_time();
819 be35facb meetecho
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, "offer", publisher->sdp);
820 5e9e29e0 meetecho
                                                JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, janus_get_monotonic_time()-start);
821 be35facb meetecho
                                                if(res != JANUS_OK) {
822
                                                        /* TODO Failed to negotiate? We should remove this listener */
823
                                                } else {
824
                                                        /* Let's wait for the setup_media event */
825
                                                }
826
                                                continue;
827
                                        }
828
                                }
829
                        } else {
830
                                JANUS_DEBUG("JSON error: invalid element (ptype)\n");
831
                                sprintf(error_cause, "JSON error: invalid element (ptype)");
832
                                goto error;
833
                        }
834
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
835
                        /* Handle this publisher */
836
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant; 
837
                        if(!strcasecmp(request_text, "configure")) {
838
                                /* Configure audio/video/bitrate for this publisher */
839
                                json_t *audio = json_object_get(root, "audio");
840
                                if(audio && !json_is_boolean(audio)) {
841
                                        JANUS_DEBUG("JSON error: invalid element (audio)\n");
842
                                        sprintf(error_cause, "JSON error: invalid value (audio)");
843
                                        goto error;
844
                                }
845
                                json_t *video = json_object_get(root, "video");
846
                                if(video && !json_is_boolean(video)) {
847
                                        JANUS_DEBUG("JSON error: invalid element (video)\n");
848
                                        sprintf(error_cause, "JSON error: invalid value (video)");
849
                                        goto error;
850
                                }
851
                                json_t *bitrate = json_object_get(root, "bitrate");
852
                                if(bitrate && !json_is_integer(bitrate)) {
853
                                        JANUS_DEBUG("JSON error: invalid element (bitrate)\n");
854
                                        sprintf(error_cause, "JSON error: invalid value (bitrate)");
855
                                        goto error;
856
                                }
857
                                if(audio) {
858
                                        participant->audio_active = json_is_true(audio);
859
                                        JANUS_PRINT("Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", participant->audio_active ? "true" : "false", participant->room->room_id, participant->user_id);
860
                                }
861
                                if(video) {
862
                                        participant->video_active = json_is_true(video);
863
                                        JANUS_PRINT("Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", participant->video_active ? "true" : "false", participant->room->room_id, participant->user_id);
864
                                }
865
                                if(bitrate) {
866
                                        participant->bitrate = json_integer_value(bitrate);
867
                                        JANUS_PRINT("Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
868
                                }
869
                                /* Done */
870
                                event = json_object();
871
                                json_object_set(event, "videoroom", json_string("event"));
872
                                json_object_set(event, "room", json_integer(participant->room->room_id));
873
                                json_object_set(event, "result", json_string("ok"));
874
                        } else if(!strcasecmp(request_text, "leave")) {
875
                                /* This publisher is leaving, tell everybody */
876
                                event = json_object();
877
                                json_object_set(event, "videoroom", json_string("event"));
878
                                json_object_set(event, "room", json_integer(participant->room->room_id));
879
                                json_object_set(event, "leaving", json_integer(participant->user_id));
880
                                char *leaving_text = json_dumps(event, JSON_INDENT(3));
881
                                GList *participants_list = g_hash_table_get_values(participant->room->participants);
882
                                GList *ps = participants_list;
883
                                while(ps) {
884
                                        janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
885
                                        if(p == participant) {
886
                                                ps = ps->next;
887
                                                continue;        /* Skip the new publisher itself */
888
                                        }
889
                                        JANUS_PRINT("Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
890
                                        JANUS_PRINT("  >> %d\n", gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL));
891
                                        ps = ps->next;
892
                                }
893
                                g_free(leaving_text);
894
                                g_list_free(participants_list);
895
                                /* Done */
896
                                participant->audio_active = 0;
897
                                participant->video_active = 0;
898
                                session->started = FALSE;
899
                                session->destroy = TRUE;
900
                        } else {
901
                                JANUS_DEBUG("Unknown request '%s'\n", request_text);
902
                                sprintf(error_cause, "Unknown request '%s'", request_text);
903
                                goto error;
904
                        }
905
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
906
                        /* Handle this listener */
907
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
908
                        if(!strcasecmp(request_text, "start")) {
909
                                /* Start/restart receiving the publisher streams */
910 5e9e29e0 meetecho
                                janus_videoroom_participant *publisher = listener->feed;
911 be35facb meetecho
                                listener->paused = FALSE;
912 5e9e29e0 meetecho
                                event = json_object();
913
                                json_object_set(event, "videoroom", json_string("event"));
914
                                json_object_set(event, "room", json_integer(publisher->room->room_id));
915
                                json_object_set(event, "result", json_string("ok"));
916
                                /* Send a FIR */
917
                                char buf[20];
918
                                memset(buf, 0, 20);
919
                                if(!publisher->firefox)
920
                                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
921
                                else
922
                                        janus_rtcp_fir_legacy((char *)&buf, 20, &publisher->fir_seq);
923
                                JANUS_PRINT("Resuming publisher, sending FIR to %s\n", publisher->display);
924
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
925
                                /* Send a PLI too, just in case... */
926
                                memset(buf, 0, 12);
927
                                janus_rtcp_pli((char *)&buf, 12);
928
                                JANUS_PRINT("Resuming publisher, sending PLI to %s\n", publisher->display);
929
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
930 be35facb meetecho
                        } else if(!strcasecmp(request_text, "pause")) {
931
                                /* Stop receiving the publisher streams for a while */
932 5e9e29e0 meetecho
                                janus_videoroom_participant *publisher = listener->feed;
933 be35facb meetecho
                                listener->paused = TRUE;
934 5e9e29e0 meetecho
                                event = json_object();
935
                                json_object_set(event, "videoroom", json_string("event"));
936
                                json_object_set(event, "room", json_integer(publisher->room->room_id));
937
                                json_object_set(event, "result", json_string("ok"));
938 be35facb meetecho
                        } else if(!strcasecmp(request_text, "leave")) {
939
                                janus_videoroom_participant *publisher = listener->feed;
940
                                if(publisher != NULL) {
941
                                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
942
                                        listener->feed = NULL;
943
                                }
944
                                event = json_object();
945
                                json_object_set(event, "videoroom", json_string("event"));
946
                                json_object_set(event, "room", json_integer(publisher->room->room_id));
947
                                json_object_set(event, "result", json_string("ok"));
948
                                session->started = FALSE;
949
                        } else {
950
                                JANUS_DEBUG("Unknown request '%s'\n", request_text);
951
                                sprintf(error_cause, "Unknown request '%s'", request_text);
952
                                goto error;
953
                        }
954
                }
955
956
                /* Prepare JSON event */
957
                JANUS_PRINT("Preparing JSON event as a reply\n");
958
                char *event_text = json_dumps(event, JSON_INDENT(3));
959
                json_decref(event);
960
                /* Any SDP to handle? */
961
                if(!msg->sdp) {
962
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL));
963
                } else {
964
                        JANUS_PRINT("This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
965
                        char *type = NULL;
966
                        if(!strcasecmp(msg->sdp_type, "offer")) {
967
                                /* We need to answer */
968
                                type = "answer";
969
                        } else if(!strcasecmp(msg->sdp_type, "answer")) {
970
                                /* We got an answer (from a listener?), no need to negotiate */
971
                                JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL));
972
                        } else {
973
                                /* TODO We don't support anything else right now... */
974
                                JANUS_DEBUG("Unknown SDP type '%s'\n", msg->sdp_type);
975
                                sprintf(error_cause, "Unknown SDP type '%s'", msg->sdp_type);
976
                                goto error;
977
                        }
978
                        if(session->participant_type == janus_videoroom_p_type_publisher) {
979
                                /* Negotiate by sending the own publisher SDP back (just to negotiate the same media stuff) */
980
                                int modified = 0;
981
                                msg->sdp = string_replace(msg->sdp, "sendrecv", "sendonly", &modified);        /* FIXME In case the browser doesn't set it correctly */
982
                                msg->sdp = string_replace(msg->sdp, "sendonly", "recvonly", &modified);
983
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
984 5e9e29e0 meetecho
                                if(strstr(msg->sdp, "Mozilla")) {
985
                                        participant->firefox = TRUE;
986
                                }
987 be35facb meetecho
                                /* How long will the gateway take to push the event? */
988 5e9e29e0 meetecho
                                gint64 start = janus_get_monotonic_time();
989 be35facb meetecho
                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, msg->sdp);
990 5e9e29e0 meetecho
                                JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, janus_get_monotonic_time()-start);
991 be35facb meetecho
                                msg->sdp = string_replace(msg->sdp, "recvonly", "sendonly", &modified);
992
                                if(res != JANUS_OK) {
993
                                        /* TODO Failed to negotiate? We should remove this publisher */
994
                                } else {
995
                                        /* Store the participant's SDP for interested listeners */
996
                                        participant->sdp = g_strdup(msg->sdp);
997
                                        /* Notify all other participants that there's a new boy in town */
998
                                        json_t *list = json_array();
999
                                        json_t *pl = json_object();
1000
                                        json_object_set_new(pl, "id", json_integer(participant->user_id));
1001
                                        json_object_set_new(pl, "display", json_string(participant->display));
1002
                                        json_array_append_new(list, pl);
1003
                                        json_t *pub = json_object();
1004
                                        json_object_set(pub, "videoroom", json_string("event"));
1005
                                        json_object_set(event, "room", json_integer(participant->room->room_id));
1006
                                        json_object_set_new(pub, "publishers", list);
1007
                                        char *pub_text = json_dumps(pub, JSON_INDENT(3));
1008
                                        json_decref(list);
1009
                                        json_decref(pub);
1010
                                        GList *participants_list = g_hash_table_get_values(participant->room->participants);
1011
                                        GList *ps = participants_list;
1012
                                        while(ps) {
1013
                                                janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
1014
                                                if(p == participant) {
1015
                                                        ps = ps->next;
1016
                                                        continue;        /* Skip the new publisher itself */
1017
                                                }
1018
                                                JANUS_PRINT("Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
1019
                                                JANUS_PRINT("  >> %d\n", gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub_text, NULL, NULL));
1020
                                                ps = ps->next;
1021
                                        }
1022
                                        g_list_free(participants_list);
1023
                                        /* Let's wait for the setup_media event */
1024
                                }
1025
                        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1026
                                /* Negotiate by sending the selected publisher SDP back */
1027
                                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
1028
                                /* FIXME We should handle the case where the participant has no SDP... */
1029
                                if(listener != NULL) {
1030
                                        janus_videoroom_participant *feed = (janus_videoroom_participant *)listener->feed;
1031
                                        if(feed != NULL && feed->sdp != NULL) {
1032
                                                /* How long will the gateway take to push the event? */
1033 5e9e29e0 meetecho
                                                gint64 start = janus_get_monotonic_time();
1034 be35facb meetecho
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, feed->sdp);
1035 5e9e29e0 meetecho
                                                JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, janus_get_monotonic_time()-start);
1036 be35facb meetecho
                                                if(res != JANUS_OK) {
1037
                                                        /* TODO Failed to negotiate? We should remove this listener */
1038
                                                } else {
1039
                                                        /* Let's wait for the setup_media event */
1040
                                                }
1041
                                        }
1042
                                }
1043
                        }
1044
                }
1045
1046
                continue;
1047
                
1048
error:
1049
                {
1050
                        if(root != NULL)
1051
                                json_decref(root);
1052
                        /* Prepare JSON error event */
1053
                        json_t *event = json_object();
1054
                        json_object_set(event, "videoroom", json_string("event"));
1055
                        json_object_set(event, "error", json_string(error_cause));
1056
                        char *event_text = json_dumps(event, JSON_INDENT(3));
1057
                        json_decref(event);
1058
                        JANUS_PRINT("Pushing event: %s\n", event_text);
1059
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL));
1060
                }
1061
        }
1062
        JANUS_DEBUG("Leaving thread\n");
1063
        return NULL;
1064
}
1065
1066
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
1067
        janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
1068
        if(!packet || !packet->data || packet->length < 1) {
1069
                JANUS_PRINT("Invalid packet...\n");
1070
                return;
1071
        }
1072
        janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
1073
        if(!listener || !listener->session) {
1074
                // JANUS_PRINT("Invalid session...\n");
1075
                return;
1076
        }
1077
        if(listener->paused) {
1078
                // JANUS_PRINT("This listener paused the stream...\n");
1079
                return;
1080
        }
1081
        janus_videoroom_session *session = listener->session;
1082
        if(!session || !session->handle) {
1083
                // JANUS_PRINT("Invalid session...\n");
1084
                return;
1085
        }
1086
        if(!session->started) {
1087
                // JANUS_PRINT("Streaming not started yet for this session...\n");
1088
                return;
1089
        }
1090
        if(gateway != NULL)        /* FIXME What about RTCP? */
1091
                gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
1092
        return;
1093
}
1094
1095
/* Easy way to replace multiple occurrences of a string with another: ALWAYS creates a NEW string */
1096
char *string_replace(char *message, char *old, char *new, int *modified)
1097
{
1098
        if(!message || !old || !new || !modified)
1099
                return NULL;
1100
        *modified = 0;
1101
        if(!strstr(message, old)) {        /* Nothing to be done (old is not there) */
1102
                return message;
1103
        }
1104
        if(!strcmp(old, new)) {        /* Nothing to be done (old=new) */
1105
                return message;
1106
        }
1107
        if(strlen(old) == strlen(new)) {        /* Just overwrite */
1108
                char *outgoing = message;
1109
                char *pos = strstr(outgoing, old), *tmp = NULL;
1110
                int i = 0;
1111
                while(pos) {
1112
                        i++;
1113
                        memcpy(pos, new, strlen(new));
1114
                        pos += strlen(old);
1115
                        tmp = strstr(pos, old);
1116
                        pos = tmp;
1117
                }
1118
                return outgoing;
1119
        } else {        /* We need to resize */
1120
                *modified = 1;
1121
                char *outgoing = strdup(message);
1122
                if(outgoing == NULL) {
1123
                        JANUS_DEBUG("Memory error!\n");
1124
                        return NULL;
1125
                }
1126
                int diff = strlen(new) - strlen(old);
1127
                /* Count occurrences */
1128
                int counter = 0;
1129
                char *pos = strstr(outgoing, old), *tmp = NULL;
1130
                while(pos) {
1131
                        counter++;
1132
                        pos += strlen(old);
1133
                        tmp = strstr(pos, old);
1134
                        pos = tmp;
1135
                }
1136
                uint16_t oldlen = strlen(outgoing)+1, newlen = oldlen + diff*counter;
1137
                *modified = diff*counter;
1138
                if(diff > 0) {        /* Resize now */
1139
                        tmp = realloc(outgoing, newlen);
1140
                        if(!tmp)
1141
                                return NULL;
1142
                        outgoing = tmp;
1143
                }
1144
                /* Replace string */
1145
                pos = strstr(outgoing, old);
1146
                while(pos) {
1147
                        if(diff > 0) {        /* Move to the right (new is larger than old) */
1148
                                uint16_t len = strlen(pos)+1;
1149
                                memmove(pos + diff, pos, len);
1150
                                memcpy(pos, new, strlen(new));
1151
                                pos += strlen(new);
1152
                                tmp = strstr(pos, old);
1153
                        } else {        /* Move to the left (new is smaller than old) */
1154
                                uint16_t len = strlen(pos - diff)+1;
1155
                                memmove(pos, pos - diff, len);
1156
                                memcpy(pos, new, strlen(new));
1157
                                pos += strlen(old);
1158
                                tmp = strstr(pos, old);
1159
                        }
1160
                        pos = tmp;
1161
                }
1162
                if(diff < 0) {        /* We skipped the resize previously (shrinking memory) */
1163
                        tmp = realloc(outgoing, newlen);
1164
                        if(!tmp)
1165
                                return NULL;
1166
                        outgoing = tmp;
1167
                }
1168
                outgoing[strlen(outgoing)] = '\0';
1169
                return outgoing;
1170
        }
1171
}