Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ 47576630

History | View | Annotate | Download (42.3 KB)

1
/*! \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
\endverbatim
43
 *
44
 * \ingroup plugins
45
 * \ref plugins
46
 */
47

    
48
#include "plugin.h"
49

    
50
#include <jansson.h>
51

    
52
#include "../config.h"
53
#include "../rtcp.h"
54

    
55

    
56
/* Plugin information */
57
#define JANUS_VIDEOROOM_VERSION                        1
58
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.1"
59
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing MCU for Janus, something like Licode."
60
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
61
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
62

    
63
/* Plugin methods */
64
janus_plugin *create(void);
65
int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
66
void janus_videoroom_destroy(void);
67
int janus_videoroom_get_version(void);
68
const char *janus_videoroom_get_version_string(void);
69
const char *janus_videoroom_get_description(void);
70
const char *janus_videoroom_get_name(void);
71
const char *janus_videoroom_get_package(void);
72
void janus_videoroom_create_session(janus_pluginession *handle, int *error);
73
void janus_videoroom_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp);
74
void janus_videoroom_setup_media(janus_pluginession *handle);
75
void janus_videoroom_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len);
76
void janus_videoroom_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len);
77
void janus_videoroom_hangup_media(janus_pluginession *handle);
78
void janus_videoroom_destroy_session(janus_pluginession *handle, int *error);
79

    
80
/* Plugin setup */
81
static janus_plugin janus_videoroom_plugin =
82
        {
83
                .init = janus_videoroom_init,
84
                .destroy = janus_videoroom_destroy,
85

    
86
                .get_version = janus_videoroom_get_version,
87
                .get_version_string = janus_videoroom_get_version_string,
88
                .get_description = janus_videoroom_get_description,
89
                .get_name = janus_videoroom_get_name,
90
                .get_package = janus_videoroom_get_package,
91
                
92
                .create_session = janus_videoroom_create_session,
93
                .handle_message = janus_videoroom_handle_message,
94
                .setup_media = janus_videoroom_setup_media,
95
                .incoming_rtp = janus_videoroom_incoming_rtp,
96
                .incoming_rtcp = janus_videoroom_incoming_rtcp,
97
                .hangup_media = janus_videoroom_hangup_media,
98
                .destroy_session = janus_videoroom_destroy_session,
99
        }; 
100

    
101
/* Plugin creator */
102
janus_plugin *create(void) {
103
        JANUS_PRINT("%s created!\n", JANUS_VIDEOROOM_NAME);
104
        return &janus_videoroom_plugin;
105
}
106

    
107

    
108
/* Useful stuff */
109
static int initialized = 0, stopping = 0;
110
static janus_callbacks *gateway = NULL;
111
static GThread *handler_thread;
112
static void *janus_videoroom_handler(void *data);
113
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
114
char *string_replace(char *message, char *old, char *new, int *modified);
115

    
116
typedef enum janus_videoroom_p_type {
117
        janus_videoroom_p_type_none = 0,
118
        janus_videoroom_p_type_subscriber,
119
        janus_videoroom_p_type_publisher,
120
} janus_videoroom_p_type;
121

    
122
typedef struct janus_videoroom_message {
123
        janus_pluginession *handle;
124
        char *transaction;
125
        char *message;
126
        char *sdp_type;
127
        char *sdp;
128
} janus_videoroom_message;
129
GQueue *messages;
130

    
131
typedef struct janus_videoroom {
132
        guint64 room_id;        /* Unique room ID */
133
        gchar *room_name;        /* Room description */
134
        int max_publishers;        /* Maximum number of concurrent publishers */
135
        uint64_t bitrate;        /* Global bitrate limit */
136
        gboolean destroy;
137
        GHashTable *participants;        /* Map of potential publishers (we get listeners from them) */
138
} janus_videoroom;
139
GHashTable *rooms;
140

    
141
typedef struct janus_videoroom_session {
142
        janus_pluginession *handle;
143
        janus_videoroom_p_type participant_type;
144
        gpointer participant;
145
        gboolean started;
146
        gboolean stopping;
147
        gboolean destroy;
148
} janus_videoroom_session;
149
GHashTable *sessions;
150

    
151
typedef struct janus_videoroom_participant {
152
        janus_videoroom_session *session;
153
        janus_videoroom *room;        /* Room */
154
        guint64 user_id;        /* Unique ID in the room */
155
        gchar *display;        /* Display name (just for fun) */
156
        gchar *sdp;                        /* The SDP this publisher negotiated, if any */
157
        gboolean audio_active;
158
        gboolean video_active;
159
        uint64_t bitrate;
160
        gint64 fir_latest;        /* Time of latest sent FIR (to avoid flooding) */
161
        gint fir_seq;                /* FIR sequence number */
162
        GSList *listeners;
163
} janus_videoroom_participant;
164

    
165
typedef struct janus_videoroom_listener {
166
        janus_videoroom_session *session;
167
        janus_videoroom *room;        /* Room */
168
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
169
        gboolean paused;
170
} janus_videoroom_listener;
171

    
172
typedef struct janus_videoroom_rtp_relay_packet {
173
        char *data;
174
        gint length;
175
        gint is_video;
176
} janus_videoroom_rtp_relay_packet;
177

    
178

    
179
/* Plugin implementation */
180
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
181
        if(stopping) {
182
                /* Still stopping from before */
183
                return -1;
184
        }
185
        if(callback == NULL || config_path == NULL) {
186
                /* Invalid arguments */
187
                return -1;
188
        }
189

    
190
        /* Read configuration */
191
        char filename[255];
192
        sprintf(filename, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
193
        JANUS_PRINT("Configuration file: %s\n", filename);
194
        janus_config *config = janus_config_parse(filename);
195
        if(config != NULL)
196
                janus_config_print(config);
197

    
198
        rooms = g_hash_table_new(NULL, NULL);
199
        sessions = g_hash_table_new(NULL, NULL);
200
        messages = g_queue_new();
201
        /* This is the callback we'll need to invoke to contact the gateway */
202
        gateway = callback;
203

    
204
        /* Parse configuration to populate the rooms list */
205
        if(config != NULL) {
206
                janus_config_category *cat = janus_config_get_categories(config);
207
                while(cat != NULL) {
208
                        if(cat->name == NULL) {
209
                                cat = cat->next;
210
                                continue;
211
                        }
212
                        JANUS_PRINT("Adding video room '%s'\n", cat->name);
213
                        janus_config_item *desc = janus_config_get_item(cat, "description");
214
                        janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
215
                        janus_config_item *maxp = janus_config_get_item(cat, "publishers");
216
                        /* Create the video mcu room */
217
                        janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
218
                        if(videoroom == NULL) {
219
                                JANUS_DEBUG("Memory error!\n");
220
                                continue;
221
                        }
222
                        videoroom->room_id = atoi(cat->name);
223
                        char *description = NULL;
224
                        if(desc != NULL && desc->value != NULL)
225
                                description = g_strdup(desc->value);
226
                        else
227
                                description = g_strdup(cat->name);
228
                        if(description == NULL) {
229
                                JANUS_DEBUG("Memory error!\n");
230
                                continue;
231
                        }
232
                        videoroom->room_name = description;
233
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
234
                        if(maxp != NULL && maxp->value != NULL)
235
                                videoroom->max_publishers = atol(maxp->value);
236
                        if(videoroom->max_publishers < 0)
237
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
238
                        videoroom->bitrate = 0;
239
                        if(bitrate != NULL && bitrate->value != NULL)
240
                                videoroom->bitrate = atol(bitrate->value);
241
                        videoroom->destroy = 0;
242
                        videoroom->participants = g_hash_table_new(NULL, NULL);
243
                        g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
244
                        JANUS_PRINT("Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
245
                        cat = cat->next;
246
                }
247
                /* Done */
248
                janus_config_destroy(config);
249
                config = NULL;
250
        }
251

    
252
        /* Show available rooms */
253
        GList *rooms_list = g_hash_table_get_values(rooms);
254
        GList *r = rooms_list;
255
        while(r) {
256
                janus_videoroom *vr = (janus_videoroom *)r->data;
257
                JANUS_PRINT("  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers);
258
                r = r->next;
259
        }
260
        g_list_free(rooms_list);
261

    
262
        initialized = 1;
263
        /* Launch the thread that will handle incoming messages */
264
        GError *error = NULL;
265
        handler_thread = g_thread_try_new("janus videoroom handler", janus_videoroom_handler, NULL, &error);
266
        if(error != NULL) {
267
                initialized = 0;
268
                /* Something went wrong... */
269
                JANUS_DEBUG("Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??");
270
                return -1;
271
        }
272
        JANUS_PRINT("%s initialized!\n", JANUS_VIDEOROOM_NAME);
273
        return 0;
274
}
275

    
276
void janus_videoroom_destroy() {
277
        if(!initialized)
278
                return;
279
        stopping = 1;
280
        if(handler_thread != NULL) {
281
                g_thread_join(handler_thread);
282
        }
283
        handler_thread = NULL;
284
        /* TODO Actually remove rooms and its participants */
285
        g_hash_table_destroy(sessions);
286
        g_hash_table_destroy(rooms);
287
        g_queue_free(messages);
288
        rooms = NULL;
289
        initialized = 0;
290
        stopping = 0;
291
        JANUS_PRINT("%s destroyed!\n", JANUS_VIDEOROOM_NAME);
292
}
293

    
294
int janus_videoroom_get_version() {
295
        return JANUS_VIDEOROOM_VERSION;
296
}
297

    
298
const char *janus_videoroom_get_version_string() {
299
        return JANUS_VIDEOROOM_VERSION_STRING;
300
}
301

    
302
const char *janus_videoroom_get_description() {
303
        return JANUS_VIDEOROOM_DESCRIPTION;
304
}
305

    
306
const char *janus_videoroom_get_name() {
307
        return JANUS_VIDEOROOM_NAME;
308
}
309

    
310
const char *janus_videoroom_get_package() {
311
        return JANUS_VIDEOROOM_PACKAGE;
312
}
313

    
314
void janus_videoroom_create_session(janus_pluginession *handle, int *error) {
315
        if(stopping || !initialized) {
316
                *error = -1;
317
                return;
318
        }        
319
        janus_videoroom_session *session = (janus_videoroom_session *)calloc(1, sizeof(janus_videoroom_session));
320
        if(session == NULL) {
321
                JANUS_DEBUG("Memory error!\n");
322
                *error = -2;
323
                return;
324
        }
325
        session->handle = handle;
326
        session->participant_type = janus_videoroom_p_type_none;
327
        session->participant = NULL;
328
        handle->plugin_handle = session;
329
        g_hash_table_insert(sessions, handle, session);
330

    
331
        return;
332
}
333

    
334
void janus_videoroom_destroy_session(janus_pluginession *handle, int *error) {
335
        if(stopping || !initialized) {
336
                *error = -1;
337
                return;
338
        }        
339
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle; 
340
        if(!session) {
341
                JANUS_DEBUG("No session associated with this handle...\n");
342
                *error = -2;
343
                return;
344
        }
345
        if(session->destroy) {
346
                JANUS_PRINT("Session already destroyed...\n");
347
                g_free(session);
348
                return;
349
        }
350
        JANUS_PRINT("Removing Video Room session...\n");
351
        /* TODO Actually clean up session, e.g., removing listener from publisher and viceversa */
352
        g_hash_table_remove(sessions, handle);
353
        if(session->participant_type == janus_videoroom_p_type_publisher) {
354
                /* TODO Get rid of this publisher and its listeners */
355
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
356
                /* TODO Detach this listener from its subscriber */
357
        }
358
        janus_videoroom_hangup_media(handle);
359
        session->destroy = TRUE;
360
        g_free(session);
361

    
362
        return;
363
}
364

    
365
void janus_videoroom_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
366
        if(stopping || !initialized)
367
                return;
368
        JANUS_PRINT("%s\n", message);
369
        janus_videoroom_message *msg = calloc(1, sizeof(janus_videoroom_message));
370
        if(msg == NULL) {
371
                JANUS_DEBUG("Memory error!\n");
372
                return;
373
        }
374
        msg->handle = handle;
375
        msg->transaction = transaction ? g_strdup(transaction) : NULL;
376
        msg->message = message;
377
        msg->sdp_type = sdp_type;
378
        msg->sdp = sdp;
379
        g_queue_push_tail(messages, msg);
380
}
381

    
382
void janus_videoroom_setup_media(janus_pluginession *handle) {
383
        JANUS_DEBUG("WebRTC media is now available\n");
384
        if(stopping || !initialized)
385
                return;
386
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
387
        if(!session) {
388
                JANUS_DEBUG("No session associated with this handle...\n");
389
                return;
390
        }
391
        if(session->destroy)
392
                return;
393
        /* Media relaying can start now */
394
        session->started = TRUE;
395
        /* If this is a listener, ask the publisher a FIR */
396
        if(session->participant && session->participant_type == janus_videoroom_p_type_subscriber) {
397
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
398
                if(l && l->feed) {
399
                        janus_videoroom_participant *p = l->feed;
400
                        if(p && p->session) {
401
                                /* Send a FIR */
402
                                char buf[20];
403
                                memset(buf, 0, 20);
404
                                janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
405
                                JANUS_PRINT("New listener available, sending FIR to %s\n", p->display);
406
                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
407
                                /* Send a PLI too, just in case... */
408
                                memset(buf, 0, 12);
409
                                janus_rtcp_pli((char *)&buf, 12);
410
                                JANUS_PRINT("New listener available, sending PLI to %s\n", p->display);
411
                                gateway->relay_rtcp(p->session->handle, 1, buf, 12);
412
                        }
413
                }
414
        }
415
}
416

    
417
void janus_videoroom_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len) {
418
        if(stopping || !initialized || !gateway)
419
                return;
420
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
421
        if(!session || session->destroy || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
422
                return;
423
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
424
        if((!video && participant->audio_active) || (video && participant->video_active)) {
425
                janus_videoroom_rtp_relay_packet packet;
426
                packet.data = buf;
427
                packet.length = len;
428
                packet.is_video = video;
429
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
430
                if(video) {
431
                        /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
432
                        gint64 now = g_get_monotonic_time();
433
                        if((now-participant->fir_latest) >= (10*G_USEC_PER_SEC)) {
434
                                /* FIXME We send a FIR every 10 seconds */
435
                                participant->fir_latest = now;
436
                                char buf[20];
437
                                memset(buf, 0, 20);
438
                                janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
439
                                JANUS_PRINT("Sending FIR to %s\n", participant->display);
440
                                gateway->relay_rtcp(handle, video, buf, 20);
441
                                /* Send a PLI too, just in case... */
442
                                memset(buf, 0, 12);
443
                                janus_rtcp_pli((char *)&buf, 12);
444
                                JANUS_PRINT("New listener available, sending PLI to %s\n", participant->display);
445
                                gateway->relay_rtcp(handle, video, buf, 12);
446
                        }
447
                }
448
        }
449
}
450

    
451
void janus_videoroom_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len) {
452
        if(stopping || !initialized || !gateway)
453
                return;
454
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
455
        if(!session || session->destroy || !session->participant || !video)
456
                return;
457
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
458
                /* FIXME Badly: we're blinding forwarding the listener RTCP t the publisher: this probably means confusing him... */
459
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
460
                if(l && l->feed) {
461
                        janus_videoroom_participant *p = l->feed;
462
                        if(p && p->session) {
463
                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
464
                        }
465
                }
466
        } else if(session->participant_type == janus_videoroom_p_type_publisher) {
467
                /* FIXME Badly: we're just bouncing the incoming RTCP back with modified REMB, we need to improve this... */
468
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
469
                if(participant->bitrate > 0)
470
                        janus_rtcp_cap_remb(buf, len, participant->bitrate);
471
                gateway->relay_rtcp(handle, video, buf, len);
472
        }
473
}
474

    
475
void janus_videoroom_hangup_media(janus_pluginession *handle) {
476
        JANUS_PRINT("No WebRTC media anymore\n");
477
        if(stopping || !initialized)
478
                return;
479
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
480
        if(!session) {
481
                JANUS_DEBUG("No session associated with this handle...\n");
482
                return;
483
        }
484
        if(session->destroy)
485
                return;
486
        /* Send an event to the browser and tell it's over */
487
        if(session->participant_type == janus_videoroom_p_type_publisher) {
488
                /* Get rid of publisher */
489
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
490
                json_t *event = json_object();
491
                json_object_set(event, "videoroom", json_string("event"));
492
                json_object_set(event, "room", json_integer(participant->room->room_id));
493
                json_object_set(event, "leaving", json_integer(participant->user_id));
494
                char *leaving_text = json_dumps(event, JSON_INDENT(3));
495
                json_decref(event);
496
                g_hash_table_remove(participant->room->participants, GUINT_TO_POINTER(participant->user_id));
497
                GList *participants_list = g_hash_table_get_values(participant->room->participants);
498
                GList *ps = participants_list;
499
                while(ps) {
500
                        janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
501
                        if(p == participant) {
502
                                ps = ps->next;
503
                                continue;        /* Skip the leaving publisher itself */
504
                        }
505
                        JANUS_PRINT("Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
506
                        JANUS_PRINT("  >> %d\n", gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL));
507
                        ps = ps->next;
508
                }
509
                g_free(leaving_text);
510
                g_list_free(participants_list);
511
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
512
                /* Get rid of listener */
513
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
514
                janus_videoroom_participant *publisher = listener->feed;
515
                if(publisher != NULL) {
516
                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
517
                        listener->feed = NULL;
518
                }
519
        }
520
}
521

    
522
/* Thread to handle incoming messages */
523
static void *janus_videoroom_handler(void *data) {
524
        JANUS_DEBUG("Joining thread\n");
525
        janus_videoroom_message *msg = NULL;
526
        char *error_cause = calloc(512, sizeof(char));        /* FIXME 512 should be enough, but anyway... */
527
        if(error_cause == NULL) {
528
                JANUS_DEBUG("Memory error!\n");
529
                return NULL;
530
        }
531
        while(initialized && !stopping) {
532
                if(!messages || (msg = g_queue_pop_head(messages)) == NULL) {
533
                        usleep(50000);
534
                        continue;
535
                }
536
                janus_videoroom_session *session = (janus_videoroom_session *)msg->handle->plugin_handle;        
537
                if(!session) {
538
                        JANUS_DEBUG("No session associated with this handle...\n");
539
                        continue;
540
                }
541
                if(session->destroy)
542
                        continue;
543
                /* Handle request */
544
                JANUS_PRINT("Handling message: %s\n", msg->message);
545
                if(msg->message == NULL) {
546
                        JANUS_DEBUG("No message??\n");
547
                        sprintf(error_cause, "%s", "No message??");
548
                        goto error;
549
                }
550
                json_error_t error;
551
                json_t *root = json_loads(msg->message, 0, &error);
552
                if(!root) {
553
                        JANUS_DEBUG("JSON error: on line %d: %s\n", error.line, error.text);
554
                        sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text);
555
                        goto error;
556
                }
557
                if(!json_is_object(root)) {
558
                        JANUS_DEBUG("JSON error: not an object\n");
559
                        sprintf(error_cause, "JSON error: not an object");
560
                        goto error;
561
                }
562
                /* Get the request first */
563
                json_t *request = json_object_get(root, "request");
564
                if(!request || !json_is_string(request)) {
565
                        JANUS_DEBUG("JSON error: invalid element (request)\n");
566
                        sprintf(error_cause, "JSON error: invalid element (request)");
567
                        goto error;
568
                }
569
                const char *request_text = json_string_value(request);
570
                json_t *event = NULL;
571
                if(!strcasecmp(request_text, "create")) {
572
                        /* Create a new videoroom */
573
                        JANUS_PRINT("Creating a new videoroom\n");
574
                        json_t *desc = json_object_get(root, "description");
575
                        if(desc && !json_is_string(desc)) {
576
                                JANUS_DEBUG("JSON error: invalid element (desc)\n");
577
                                sprintf(error_cause, "JSON error: invalid element (desc)");
578
                                goto error;
579
                        }
580
                        json_t *bitrate = json_object_get(root, "bitrate");
581
                        if(bitrate && !json_is_integer(bitrate)) {
582
                                JANUS_DEBUG("JSON error: invalid element (bitrate)\n");
583
                                sprintf(error_cause, "JSON error: invalid element (bitrate)");
584
                                goto error;
585
                        }
586
                        json_t *publishers = json_object_get(root, "publishers");
587
                        if(publishers && !json_is_integer(publishers)) {
588
                                JANUS_DEBUG("JSON error: invalid element (publishers)\n");
589
                                sprintf(error_cause, "JSON error: invalid element (publishers)");
590
                                goto error;
591
                        }
592
                        /* Create the audio bridge room */
593
                        janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
594
                        if(videoroom == NULL) {
595
                                JANUS_DEBUG("Memory error!\n");
596
                                sprintf(error_cause, "Memory error");
597
                                goto error;
598
                        }
599
                        /* Generate a random ID */
600
                        guint64 room_id = 0;
601
                        while(room_id == 0) {
602
                                room_id = g_random_int();
603
                                if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
604
                                        /* Room ID already taken, try another one */
605
                                        room_id = 0;
606
                                }
607
                        }
608
                        videoroom->room_id = room_id;
609
                        char *description = NULL;
610
                        if(desc != NULL) {
611
                                description = g_strdup(json_string_value(desc));
612
                        } else {
613
                                char roomname[255];
614
                                sprintf(roomname, "Room %"SCNu64"", videoroom->room_id);
615
                                description = g_strdup(roomname);
616
                        }
617
                        if(description == NULL) {
618
                                JANUS_DEBUG("Memory error!\n");
619
                                continue;
620
                        }
621
                        videoroom->room_name = description;
622
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
623
                        if(publishers)
624
                                videoroom->max_publishers = json_integer_value(publishers);
625
                        if(videoroom->max_publishers < 0)
626
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
627
                        videoroom->bitrate = 0;
628
                        if(bitrate)
629
                                videoroom->bitrate = json_integer_value(bitrate);
630
                        videoroom->destroy = 0;
631
                        videoroom->participants = g_hash_table_new(NULL, NULL);
632
                        g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
633
                        JANUS_PRINT("Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
634
                        /* Show updated rooms list */
635
                        GList *rooms_list = g_hash_table_get_values(rooms);
636
                        GList *r = rooms_list;
637
                        while(r) {
638
                                janus_videoroom *vr = (janus_videoroom *)r->data;
639
                                JANUS_PRINT("  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers);
640
                                r = r->next;
641
                        }
642
                        g_list_free(rooms_list);
643
                        /* Send info back */
644
                        event = json_object();
645
                        json_object_set(event, "videoroom", json_string("created"));
646
                        json_object_set(event, "room", json_integer(videoroom->room_id));
647
                } else
648
                /* What kind of participant is this session referring to? */
649
                if(session->participant_type == janus_videoroom_p_type_none) {
650
                        JANUS_PRINT("Configuring new participant\n");
651
                        /* Not configured yet, we need to do this now */
652
                        if(strcasecmp(request_text, "join")) {
653
                                JANUS_DEBUG("Invalid request on unconfigured participant\n");
654
                                sprintf(error_cause, "Invalid request on unconfigured participant");
655
                                goto error;
656
                        }
657
                        json_t *room = json_object_get(root, "room");
658
                        if(!room || !json_is_integer(room)) {
659
                                JANUS_DEBUG("JSON error: invalid element (room)\n");
660
                                sprintf(error_cause, "JSON error: invalid element (room)");
661
                                goto error;
662
                        }
663
                        guint64 room_id = json_integer_value(room);
664
                        janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
665
                        if(videoroom == NULL) {
666
                                JANUS_DEBUG("No such room (%"SCNu64")\n", room_id);
667
                                sprintf(error_cause, "No such room (%"SCNu64")", room_id);
668
                                goto error;
669
                        }
670
                        json_t *ptype = json_object_get(root, "ptype");
671
                        if(!ptype || !json_is_string(ptype)) {
672
                                JANUS_DEBUG("JSON error: invalid element (ptype)\n");
673
                                sprintf(error_cause, "JSON error: invalid element (ptype)");
674
                                goto error;
675
                        }
676
                        const char *ptype_text = json_string_value(ptype);
677
                        if(!strcasecmp(ptype_text, "publisher")) {
678
                                JANUS_PRINT("Configuring new publisher\n");
679
                                /* This is a new publisher: is there room? */
680
                                GList *participants_list = g_hash_table_get_values(videoroom->participants);
681
                                if(g_list_length(participants_list) == videoroom->max_publishers) {
682
                                        JANUS_DEBUG("Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
683
                                        sprintf(error_cause, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
684
                                        g_list_free(participants_list);
685
                                        goto error;
686
                                }
687
                                g_list_free(participants_list);
688
                                json_t *display = json_object_get(root, "display");
689
                                if(!display || !json_is_string(display)) {
690
                                        JANUS_DEBUG("JSON error: invalid element (display)\n");
691
                                        sprintf(error_cause, "JSON error: invalid element (display)");
692
                                        goto error;
693
                                }
694
                                const char *display_text = json_string_value(display);
695
                                /* Generate a random ID */
696
                                guint64 user_id = 0;
697
                                while(user_id == 0) {
698
                                        user_id = g_random_int();
699
                                        if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
700
                                                /* User ID already taken, try another one */
701
                                                user_id = 0;
702
                                        }
703
                                }
704
                                JANUS_PRINT("  -- Publisher ID: %"SCNu64"\n", user_id);
705
                                janus_videoroom_participant *publisher = calloc(1, sizeof(janus_videoroom_participant));
706
                                if(publisher == NULL) {
707
                                        JANUS_DEBUG("Memory error!\n");
708
                                        sprintf(error_cause, "Memory error");
709
                                        goto error;
710
                                }
711
                                publisher->session = session;
712
                                publisher->room = videoroom;
713
                                publisher->user_id = user_id;
714
                                publisher->display = g_strdup(display_text);
715
                                if(publisher->display == NULL) {
716
                                        JANUS_DEBUG("Memory error!\n");
717
                                        sprintf(error_cause, "Memory error");
718
                                        g_free(publisher);
719
                                        goto error;
720
                                }
721
                                publisher->sdp = NULL;        /* We'll deal with this later */
722
                                publisher->audio_active = FALSE;
723
                                publisher->video_active = FALSE;
724
                                publisher->bitrate = videoroom->bitrate;
725
                                publisher->listeners = NULL;
726
                                publisher->fir_latest = 0;
727
                                publisher->fir_seq = 0;
728
                                /* Done */
729
                                session->participant_type = janus_videoroom_p_type_publisher;
730
                                session->participant = publisher;
731
                                g_hash_table_insert(videoroom->participants, GUINT_TO_POINTER(user_id), publisher);
732
                                /* Return a list of all available publishers (those with an SDP available, that is) */
733
                                json_t *list = json_array();
734
                                participants_list = g_hash_table_get_values(videoroom->participants);
735
                                GList *ps = participants_list;
736
                                while(ps) {
737
                                        janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
738
                                        if(p == publisher || !p->sdp) {
739
                                                ps = ps->next;
740
                                                continue;
741
                                        }
742
                                        json_t *pl = json_object();
743
                                        json_object_set_new(pl, "id", json_integer(p->user_id));
744
                                        json_object_set_new(pl, "display", json_string(p->display));
745
                                        json_array_append_new(list, pl);
746
                                        ps = ps->next;
747
                                }
748
                                event = json_object();
749
                                json_object_set(event, "videoroom", json_string("joined"));
750
                                json_object_set(event, "room", json_integer(videoroom->room_id));
751
                                json_object_set(event, "id", json_integer(user_id));
752
                                json_object_set_new(event, "publishers", list);
753
                                g_list_free(participants_list);
754
                        } else if(!strcasecmp(ptype_text, "listener")) {
755
                                JANUS_PRINT("Configuring new listener\n");
756
                                /* This is a new listener */
757
                                json_t *feed = json_object_get(root, "feed");
758
                                if(!feed || !json_is_integer(feed)) {
759
                                        JANUS_DEBUG("JSON error: invalid element (feed)\n");
760
                                        sprintf(error_cause, "JSON error: invalid element (feed)");
761
                                        goto error;
762
                                }
763
                                guint64 feed_id = json_integer_value(feed);
764
                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
765
                                if(publisher == NULL || publisher->sdp == NULL) {
766
                                        JANUS_DEBUG("No such feed (%"SCNu64")\n", feed_id);
767
                                        sprintf(error_cause, "No such feed (%"SCNu64")", feed_id);
768
                                        goto error;
769
                                } else {
770
                                        janus_videoroom_listener *listener = calloc(1, sizeof(janus_videoroom_listener));
771
                                        if(listener == NULL) {
772
                                                JANUS_DEBUG("Memory error!\n");
773
                                                sprintf(error_cause, "Memory error");
774
                                                goto error;
775
                                        }
776
                                        listener->session = session;
777
                                        listener->room = videoroom;
778
                                        listener->feed = publisher;
779
                                        listener->paused = TRUE;        /* We need an explicit start from the listener */
780
                                        session->participant = listener;
781
                                        publisher->listeners = g_slist_append(publisher->listeners, listener);
782
                                        event = json_object();
783
                                        json_object_set(event, "videoroom", json_string("attached"));
784
                                        json_object_set(event, "room", json_integer(videoroom->room_id));
785
                                        json_object_set(event, "id", json_integer(feed_id));
786
                                        json_object_set(event, "display", json_string(publisher->display));
787
                                        session->participant_type = janus_videoroom_p_type_subscriber;
788
                                        JANUS_PRINT("Preparing JSON event as a reply\n");
789
                                        char *event_text = json_dumps(event, JSON_INDENT(3));
790
                                        json_decref(event);
791
                                        /* Negotiate by sending the selected publisher SDP back */
792
                                        if(publisher->sdp != NULL) {
793
                                                /* How long will the gateway take to push the event? */
794
                                                gint64 start = g_get_monotonic_time();
795
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, "offer", publisher->sdp);
796
                                                JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, g_get_monotonic_time()-start);
797
                                                if(res != JANUS_OK) {
798
                                                        /* TODO Failed to negotiate? We should remove this listener */
799
                                                } else {
800
                                                        /* Let's wait for the setup_media event */
801
                                                }
802
                                                continue;
803
                                        }
804
                                }
805
                        } else {
806
                                JANUS_DEBUG("JSON error: invalid element (ptype)\n");
807
                                sprintf(error_cause, "JSON error: invalid element (ptype)");
808
                                goto error;
809
                        }
810
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
811
                        /* Handle this publisher */
812
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant; 
813
                        if(!strcasecmp(request_text, "configure")) {
814
                                /* Configure audio/video/bitrate for this publisher */
815
                                json_t *audio = json_object_get(root, "audio");
816
                                if(audio && !json_is_boolean(audio)) {
817
                                        JANUS_DEBUG("JSON error: invalid element (audio)\n");
818
                                        sprintf(error_cause, "JSON error: invalid value (audio)");
819
                                        goto error;
820
                                }
821
                                json_t *video = json_object_get(root, "video");
822
                                if(video && !json_is_boolean(video)) {
823
                                        JANUS_DEBUG("JSON error: invalid element (video)\n");
824
                                        sprintf(error_cause, "JSON error: invalid value (video)");
825
                                        goto error;
826
                                }
827
                                json_t *bitrate = json_object_get(root, "bitrate");
828
                                if(bitrate && !json_is_integer(bitrate)) {
829
                                        JANUS_DEBUG("JSON error: invalid element (bitrate)\n");
830
                                        sprintf(error_cause, "JSON error: invalid value (bitrate)");
831
                                        goto error;
832
                                }
833
                                if(audio) {
834
                                        participant->audio_active = json_is_true(audio);
835
                                        JANUS_PRINT("Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", participant->audio_active ? "true" : "false", participant->room->room_id, participant->user_id);
836
                                }
837
                                if(video) {
838
                                        participant->video_active = json_is_true(video);
839
                                        JANUS_PRINT("Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", participant->video_active ? "true" : "false", participant->room->room_id, participant->user_id);
840
                                }
841
                                if(bitrate) {
842
                                        participant->bitrate = json_integer_value(bitrate);
843
                                        JANUS_PRINT("Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
844
                                }
845
                                /* Done */
846
                                event = json_object();
847
                                json_object_set(event, "videoroom", json_string("event"));
848
                                json_object_set(event, "room", json_integer(participant->room->room_id));
849
                                json_object_set(event, "result", json_string("ok"));
850
                        } else if(!strcasecmp(request_text, "leave")) {
851
                                /* This publisher is leaving, tell everybody */
852
                                event = json_object();
853
                                json_object_set(event, "videoroom", json_string("event"));
854
                                json_object_set(event, "room", json_integer(participant->room->room_id));
855
                                json_object_set(event, "leaving", json_integer(participant->user_id));
856
                                char *leaving_text = json_dumps(event, JSON_INDENT(3));
857
                                GList *participants_list = g_hash_table_get_values(participant->room->participants);
858
                                GList *ps = participants_list;
859
                                while(ps) {
860
                                        janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
861
                                        if(p == participant) {
862
                                                ps = ps->next;
863
                                                continue;        /* Skip the new publisher itself */
864
                                        }
865
                                        JANUS_PRINT("Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
866
                                        JANUS_PRINT("  >> %d\n", gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL));
867
                                        ps = ps->next;
868
                                }
869
                                g_free(leaving_text);
870
                                g_list_free(participants_list);
871
                                /* Done */
872
                                participant->audio_active = 0;
873
                                participant->video_active = 0;
874
                                session->started = FALSE;
875
                                session->destroy = TRUE;
876
                        } else {
877
                                JANUS_DEBUG("Unknown request '%s'\n", request_text);
878
                                sprintf(error_cause, "Unknown request '%s'", request_text);
879
                                goto error;
880
                        }
881
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
882
                        /* Handle this listener */
883
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
884
                        if(!strcasecmp(request_text, "start")) {
885
                                /* Start/restart receiving the publisher streams */
886
                                listener->paused = FALSE;
887
                        } else if(!strcasecmp(request_text, "pause")) {
888
                                /* Stop receiving the publisher streams for a while */
889
                                listener->paused = TRUE;
890
                        } else if(!strcasecmp(request_text, "leave")) {
891
                                janus_videoroom_participant *publisher = listener->feed;
892
                                if(publisher != NULL) {
893
                                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
894
                                        listener->feed = NULL;
895
                                }
896
                                event = json_object();
897
                                json_object_set(event, "videoroom", json_string("event"));
898
                                json_object_set(event, "room", json_integer(publisher->room->room_id));
899
                                json_object_set(event, "result", json_string("ok"));
900
                                session->started = FALSE;
901
                        } else {
902
                                JANUS_DEBUG("Unknown request '%s'\n", request_text);
903
                                sprintf(error_cause, "Unknown request '%s'", request_text);
904
                                goto error;
905
                        }
906
                }
907

    
908
                /* Prepare JSON event */
909
                JANUS_PRINT("Preparing JSON event as a reply\n");
910
                char *event_text = json_dumps(event, JSON_INDENT(3));
911
                json_decref(event);
912
                /* Any SDP to handle? */
913
                if(!msg->sdp) {
914
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL));
915
                } else {
916
                        JANUS_PRINT("This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
917
                        char *type = NULL;
918
                        if(!strcasecmp(msg->sdp_type, "offer")) {
919
                                /* We need to answer */
920
                                type = "answer";
921
                        } else if(!strcasecmp(msg->sdp_type, "answer")) {
922
                                /* We got an answer (from a listener?), no need to negotiate */
923
                                JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL));
924
                        } else {
925
                                /* TODO We don't support anything else right now... */
926
                                JANUS_DEBUG("Unknown SDP type '%s'\n", msg->sdp_type);
927
                                sprintf(error_cause, "Unknown SDP type '%s'", msg->sdp_type);
928
                                goto error;
929
                        }
930
                        if(session->participant_type == janus_videoroom_p_type_publisher) {
931
                                /* Negotiate by sending the own publisher SDP back (just to negotiate the same media stuff) */
932
                                int modified = 0;
933
                                msg->sdp = string_replace(msg->sdp, "sendrecv", "sendonly", &modified);        /* FIXME In case the browser doesn't set it correctly */
934
                                msg->sdp = string_replace(msg->sdp, "sendonly", "recvonly", &modified);
935
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
936
                                /* How long will the gateway take to push the event? */
937
                                gint64 start = g_get_monotonic_time();
938
                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, msg->sdp);
939
                                JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, g_get_monotonic_time()-start);
940
                                msg->sdp = string_replace(msg->sdp, "recvonly", "sendonly", &modified);
941
                                if(res != JANUS_OK) {
942
                                        /* TODO Failed to negotiate? We should remove this publisher */
943
                                } else {
944
                                        /* Store the participant's SDP for interested listeners */
945
                                        participant->sdp = g_strdup(msg->sdp);
946
                                        /* Notify all other participants that there's a new boy in town */
947
                                        json_t *list = json_array();
948
                                        json_t *pl = json_object();
949
                                        json_object_set_new(pl, "id", json_integer(participant->user_id));
950
                                        json_object_set_new(pl, "display", json_string(participant->display));
951
                                        json_array_append_new(list, pl);
952
                                        json_t *pub = json_object();
953
                                        json_object_set(pub, "videoroom", json_string("event"));
954
                                        json_object_set(event, "room", json_integer(participant->room->room_id));
955
                                        json_object_set_new(pub, "publishers", list);
956
                                        char *pub_text = json_dumps(pub, JSON_INDENT(3));
957
                                        json_decref(list);
958
                                        json_decref(pub);
959
                                        GList *participants_list = g_hash_table_get_values(participant->room->participants);
960
                                        GList *ps = participants_list;
961
                                        while(ps) {
962
                                                janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
963
                                                if(p == participant) {
964
                                                        ps = ps->next;
965
                                                        continue;        /* Skip the new publisher itself */
966
                                                }
967
                                                JANUS_PRINT("Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
968
                                                JANUS_PRINT("  >> %d\n", gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub_text, NULL, NULL));
969
                                                ps = ps->next;
970
                                        }
971
                                        g_list_free(participants_list);
972
                                        /* Let's wait for the setup_media event */
973
                                }
974
                        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
975
                                /* Negotiate by sending the selected publisher SDP back */
976
                                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
977
                                /* FIXME We should handle the case where the participant has no SDP... */
978
                                if(listener != NULL) {
979
                                        janus_videoroom_participant *feed = (janus_videoroom_participant *)listener->feed;
980
                                        if(feed != NULL && feed->sdp != NULL) {
981
                                                /* How long will the gateway take to push the event? */
982
                                                gint64 start = g_get_monotonic_time();
983
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, feed->sdp);
984
                                                JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, g_get_monotonic_time()-start);
985
                                                if(res != JANUS_OK) {
986
                                                        /* TODO Failed to negotiate? We should remove this listener */
987
                                                } else {
988
                                                        /* Let's wait for the setup_media event */
989
                                                }
990
                                        }
991
                                }
992
                        }
993
                }
994

    
995
                continue;
996
                
997
error:
998
                {
999
                        if(root != NULL)
1000
                                json_decref(root);
1001
                        /* Prepare JSON error event */
1002
                        json_t *event = json_object();
1003
                        json_object_set(event, "videoroom", json_string("event"));
1004
                        json_object_set(event, "error", json_string(error_cause));
1005
                        char *event_text = json_dumps(event, JSON_INDENT(3));
1006
                        json_decref(event);
1007
                        JANUS_PRINT("Pushing event: %s\n", event_text);
1008
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL));
1009
                }
1010
        }
1011
        JANUS_DEBUG("Leaving thread\n");
1012
        return NULL;
1013
}
1014

    
1015
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
1016
        janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
1017
        if(!packet || !packet->data || packet->length < 1) {
1018
                JANUS_PRINT("Invalid packet...\n");
1019
                return;
1020
        }
1021
        janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
1022
        if(!listener || !listener->session) {
1023
                // JANUS_PRINT("Invalid session...\n");
1024
                return;
1025
        }
1026
        if(listener->paused) {
1027
                // JANUS_PRINT("This listener paused the stream...\n");
1028
                return;
1029
        }
1030
        janus_videoroom_session *session = listener->session;
1031
        if(!session || !session->handle) {
1032
                // JANUS_PRINT("Invalid session...\n");
1033
                return;
1034
        }
1035
        if(!session->started) {
1036
                // JANUS_PRINT("Streaming not started yet for this session...\n");
1037
                return;
1038
        }
1039
        if(gateway != NULL)        /* FIXME What about RTCP? */
1040
                gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
1041
        return;
1042
}
1043

    
1044
/* Easy way to replace multiple occurrences of a string with another: ALWAYS creates a NEW string */
1045
char *string_replace(char *message, char *old, char *new, int *modified)
1046
{
1047
        if(!message || !old || !new || !modified)
1048
                return NULL;
1049
        *modified = 0;
1050
        if(!strstr(message, old)) {        /* Nothing to be done (old is not there) */
1051
                return message;
1052
        }
1053
        if(!strcmp(old, new)) {        /* Nothing to be done (old=new) */
1054
                return message;
1055
        }
1056
        if(strlen(old) == strlen(new)) {        /* Just overwrite */
1057
                char *outgoing = message;
1058
                char *pos = strstr(outgoing, old), *tmp = NULL;
1059
                int i = 0;
1060
                while(pos) {
1061
                        i++;
1062
                        memcpy(pos, new, strlen(new));
1063
                        pos += strlen(old);
1064
                        tmp = strstr(pos, old);
1065
                        pos = tmp;
1066
                }
1067
                return outgoing;
1068
        } else {        /* We need to resize */
1069
                *modified = 1;
1070
                char *outgoing = strdup(message);
1071
                if(outgoing == NULL) {
1072
                        JANUS_DEBUG("Memory error!\n");
1073
                        return NULL;
1074
                }
1075
                int diff = strlen(new) - strlen(old);
1076
                /* Count occurrences */
1077
                int counter = 0;
1078
                char *pos = strstr(outgoing, old), *tmp = NULL;
1079
                while(pos) {
1080
                        counter++;
1081
                        pos += strlen(old);
1082
                        tmp = strstr(pos, old);
1083
                        pos = tmp;
1084
                }
1085
                uint16_t oldlen = strlen(outgoing)+1, newlen = oldlen + diff*counter;
1086
                *modified = diff*counter;
1087
                if(diff > 0) {        /* Resize now */
1088
                        tmp = realloc(outgoing, newlen);
1089
                        if(!tmp)
1090
                                return NULL;
1091
                        outgoing = tmp;
1092
                }
1093
                /* Replace string */
1094
                pos = strstr(outgoing, old);
1095
                while(pos) {
1096
                        if(diff > 0) {        /* Move to the right (new is larger than old) */
1097
                                uint16_t len = strlen(pos)+1;
1098
                                memmove(pos + diff, pos, len);
1099
                                memcpy(pos, new, strlen(new));
1100
                                pos += strlen(new);
1101
                                tmp = strstr(pos, old);
1102
                        } else {        /* Move to the left (new is smaller than old) */
1103
                                uint16_t len = strlen(pos - diff)+1;
1104
                                memmove(pos, pos - diff, len);
1105
                                memcpy(pos, new, strlen(new));
1106
                                pos += strlen(old);
1107
                                tmp = strstr(pos, old);
1108
                        }
1109
                        pos = tmp;
1110
                }
1111
                if(diff < 0) {        /* We skipped the resize previously (shrinking memory) */
1112
                        tmp = realloc(outgoing, newlen);
1113
                        if(!tmp)
1114
                                return NULL;
1115
                        outgoing = tmp;
1116
                }
1117
                outgoing[strlen(outgoing)] = '\0';
1118
                return outgoing;
1119
        }
1120
}