Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videocall.c @ 3a3cc054

History | View | Annotate | Download (42.6 KB)

1
/*! \file   janus_videocall.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief  Janus VideoCall plugin
5
 * \details  This is a simple video call plugin for Janus, allowing two
6
 * WebRTC peers to call each other through the gateway. The idea is to
7
 * provide a similar service as the well known AppRTC demo (https://apprtc.appspot.com),
8
 * but with the media flowing through the gateway rather than being peer-to-peer.
9
 * 
10
 * The plugin provides a simple fake registration mechanism. A peer attaching
11
 * to the plugin needs to specify a username, which acts as a "phone number":
12
 * if the username is free, it is associated with the peer, which means
13
 * he/she can be "called" using that username by another peer. Peers can
14
 * either "call" another peer, by specifying their username, or wait for a call.
15
 * The approach used by this plugin is similar to the one employed by the
16
 * echo test one: all frames (RTP/RTCP) coming from one peer are relayed
17
 * to the other.
18
 * 
19
 * Just as in the janus_videocall.c plugin, there are knobs to control
20
 * whether audio and/or video should be muted or not, and if the bitrate
21
 * of the peer needs to be capped by means of REMB messages.
22
 * 
23
 * \section vcallapi Video Call API
24
 * 
25
 * All requests you can send in the Video Call API are asynchronous,
26
 * which means all responses (successes and errors) will be delivered
27
 * as events with the same transaction. 
28
 * 
29
 * The supported requests are \c list , \c register , \c call ,
30
 * \c accept , \c set and \c hangup . \c list allows you to get a list
31
 * of all the registered peers; \c register can be used to register
32
 * a username to call and be called; \c call is used to start a video
33
 * call with somebody through the plugin, while \c accept is used to
34
 * accept the call in case one is invited instead of inviting; \c set
35
 * can be used to configure some call-related settings (e.g., a cap on
36
 * the send bandwidth); finally, \c hangup can be used to terminate the
37
 * communication at any time, either to hangup an ongoing call or to
38
 * cancel/decline a call that hasn't started yet.
39
 * 
40
 * The \c list request has to be formatted as follows:
41
 * 
42
\verbatim
43
{
44
        "request" : "list"
45
}
46
\endverbatim
47
 *
48
 * A successful request will result in an array of peers to be returned:
49
 * 
50
\verbatim
51
{
52
        "videocall" : "event",
53
        "result" : {
54
                "list": [        // Array of peers
55
                        "alice78",
56
                        "bob51",
57
                        // others
58
                ]
59
        }
60
}
61
\endverbatim
62
 * 
63
 * An error instead (and the same applies to all other requests, so this
64
 * won't be repeated) would provide both an error code and a more verbose
65
 * description of the cause of the issue:
66
 * 
67
\verbatim
68
{
69
        "videocall" : "event",
70
        "error_code" : <numeric ID, check Macros below>,
71
        "error" : "<error description as a string>"
72
}
73
\endverbatim
74
 * 
75
 * To register a username to call and be called, the \c register request
76
 * can be used. This works on a "first come, first served" basis: there's
77
 * no authetication involved, you just specify the username you'd like
78
 * to use and, if free, it's assigned to you. The \c request has to be
79
 * formatted as follows:
80
 * 
81
\verbatim
82
{
83
        "request" : "register",
84
        "username" : "<desired unique username>"
85
}
86
\endverbatim
87
 * 
88
 * If successul, this will result in a \c registered event:
89
 * 
90
\verbatim
91
{
92
        "videocall" : "event",
93
        "result" : {
94
                "event" : "registered",
95
                "username" : "<same username, registered>"
96
        }
97
}
98
\endverbatim
99
 * 
100
 * Once you're registered, you can either start a new call or wait to
101
 * be called by someone else who knows your username. To start a new
102
 * call, the \c call request can be used: this request must be attached
103
 * to a JSEP offer containing the WebRTC-related info to setup a new
104
 * media session. A \c call request has to be formatted as follows:
105
 * 
106
\verbatim
107
{
108
        "request" : "call",
109
        "username" : "<username to call>"
110
}
111
\endverbatim
112
 * 
113
 * If successul, this will result in a \c calling event:
114
 * 
115
\verbatim
116
{
117
        "videocall" : "event",
118
        "result" : {
119
                "event" : "calling",
120
                "username" : "<same username, registered>"
121
        }
122
}
123
\endverbatim
124
 *
125
 * At the same time, the user being called will receive an
126
 * \c incomingcall event
127
 *  
128
\verbatim
129
{
130
        "videocall" : "event",
131
        "result" : {
132
                "event" : "incomingcall",
133
                "username" : "<your username>"
134
        }
135
}
136
\endverbatim
137
 * 
138
 * To accept the call, the \c accept request can be used. This request
139
 * must be attached to a JSEP answer containing the WebRTC-related
140
 * information to complete the actual PeerConnection setup. A \c accept
141
 * request has to be formatted as follows:
142
 * 
143
\verbatim
144
{
145
        "request" : "accept"
146
}
147
\endverbatim
148
 * 
149
 * If successul, both the caller and the callee will receive an
150
 * \c accepted event to notify them about the success of the signalling:
151
 * 
152
\verbatim
153
{
154
        "videocall" : "event",
155
        "result" : {
156
                "event" : "accepted",
157
                "username" : "<caller username>"
158
        }
159
}
160
\endverbatim
161
 *
162
 * At this point, the media-related settings of the call can be modified
163
 * on either side by means of a \c set request, which acts pretty much
164
 * as the one in the \ref echoapi . The \c set request has to be
165
 * formatted as follows:
166
 *
167
\verbatim
168
{
169
        "request" : "set",
170
        "audio" : true|false,
171
        "video" : true|false,
172
        "bitrate" : <numeric bitrate value>
173
}
174
\endverbatim
175
 *
176
 * \c audio instructs the plugin to do or do not relay audio frames;
177
 * \c video does the same for video; \c bitrate caps the bandwidth to
178
 * force on the browser encoding side (e.g., 128000 for 128kbps).
179
 * A successful request will result in a \c set event:
180
 * 
181
\verbatim
182
{
183
        "videocall" : "event",
184
        "result" : {
185
                "event" : "set"
186
        }
187
}
188
\endverbatim
189
 * 
190
 * To decline an incoming call, cancel an attempt to call or simply
191
 * hangup an ongoing conversation, the \c hangup request can be used,
192
 * which has to be formatted as follows:
193
 * 
194
\verbatim
195
{
196
        "request" : "hangup"
197
}
198
\endverbatim
199
 *
200
 * Whatever the reason of a call being closed (e.g., a \c hangup request,
201
 * a PeerConnection being closed, or something else), both parties in
202
 * the communication will receive a \c hangup event:
203
 * 
204
\verbatim
205
{
206
        "videocall" : "event",
207
        "result" : {
208
                "event" : "hangup",
209
                "username" : "<username of who closed the communication>",
210
                "reason" : "<description of what happened>"
211
        }
212
}
213
\endverbatim
214
 * 
215
 * \ingroup plugins
216
 * \ref plugins
217
 */
218

    
219
#include "plugin.h"
220

    
221
#include <jansson.h>
222

    
223
#include "../debug.h"
224
#include "../apierror.h"
225
#include "../config.h"
226
#include "../mutex.h"
227
#include "../rtcp.h"
228
#include "../utils.h"
229

    
230

    
231
/* Plugin information */
232
#define JANUS_VIDEOCALL_VERSION                        5
233
#define JANUS_VIDEOCALL_VERSION_STRING        "0.0.5"
234
#define JANUS_VIDEOCALL_DESCRIPTION                "This is a simple video call plugin for Janus, allowing two WebRTC peers to call each other through the gateway."
235
#define JANUS_VIDEOCALL_NAME                        "JANUS VideoCall plugin"
236
#define JANUS_VIDEOCALL_AUTHOR                        "Meetecho s.r.l."
237
#define JANUS_VIDEOCALL_PACKAGE                        "janus.plugin.videocall"
238

    
239
/* Plugin methods */
240
janus_plugin *create(void);
241
int janus_videocall_init(janus_callbacks *callback, const char *config_path);
242
void janus_videocall_destroy(void);
243
int janus_videocall_get_api_compatibility(void);
244
int janus_videocall_get_version(void);
245
const char *janus_videocall_get_version_string(void);
246
const char *janus_videocall_get_description(void);
247
const char *janus_videocall_get_name(void);
248
const char *janus_videocall_get_author(void);
249
const char *janus_videocall_get_package(void);
250
void janus_videocall_create_session(janus_plugin_session *handle, int *error);
251
struct janus_plugin_result *janus_videocall_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp);
252
void janus_videocall_setup_media(janus_plugin_session *handle);
253
void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
254
void janus_videocall_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
255
void janus_videocall_incoming_data(janus_plugin_session *handle, char *buf, int len);
256
void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video);
257
void janus_videocall_hangup_media(janus_plugin_session *handle);
258
void janus_videocall_destroy_session(janus_plugin_session *handle, int *error);
259
char *janus_videocall_query_session(janus_plugin_session *handle);
260

    
261
/* Plugin setup */
262
static janus_plugin janus_videocall_plugin =
263
        JANUS_PLUGIN_INIT (
264
                .init = janus_videocall_init,
265
                .destroy = janus_videocall_destroy,
266

    
267
                .get_api_compatibility = janus_videocall_get_api_compatibility,
268
                .get_version = janus_videocall_get_version,
269
                .get_version_string = janus_videocall_get_version_string,
270
                .get_description = janus_videocall_get_description,
271
                .get_name = janus_videocall_get_name,
272
                .get_author = janus_videocall_get_author,
273
                .get_package = janus_videocall_get_package,
274
                
275
                .create_session = janus_videocall_create_session,
276
                .handle_message = janus_videocall_handle_message,
277
                .setup_media = janus_videocall_setup_media,
278
                .incoming_rtp = janus_videocall_incoming_rtp,
279
                .incoming_rtcp = janus_videocall_incoming_rtcp,
280
                .incoming_data = janus_videocall_incoming_data,
281
                .slow_link = janus_videocall_slow_link,
282
                .hangup_media = janus_videocall_hangup_media,
283
                .destroy_session = janus_videocall_destroy_session,
284
                .query_session = janus_videocall_query_session,
285
        );
286

    
287
/* Plugin creator */
288
janus_plugin *create(void) {
289
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOCALL_NAME);
290
        return &janus_videocall_plugin;
291
}
292

    
293

    
294
/* Useful stuff */
295
static volatile gint initialized = 0, stopping = 0;
296
static janus_callbacks *gateway = NULL;
297
static GThread *handler_thread;
298
static GThread *watchdog;
299
static void *janus_videocall_handler(void *data);
300

    
301
typedef struct janus_videocall_message {
302
        janus_plugin_session *handle;
303
        char *transaction;
304
        char *message;
305
        char *sdp_type;
306
        char *sdp;
307
} janus_videocall_message;
308
static GAsyncQueue *messages = NULL;
309

    
310
void janus_videocall_message_free(janus_videocall_message *msg);
311
void janus_videocall_message_free(janus_videocall_message *msg) {
312
        if(!msg)
313
                return;
314

    
315
        msg->handle = NULL;
316

    
317
        g_free(msg->transaction);
318
        msg->transaction = NULL;
319
        g_free(msg->message);
320
        msg->message = NULL;
321
        g_free(msg->sdp_type);
322
        msg->sdp_type = NULL;
323
        g_free(msg->sdp);
324
        msg->sdp = NULL;
325

    
326
        g_free(msg);
327
}
328

    
329
typedef struct janus_videocall_session {
330
        janus_plugin_session *handle;
331
        gchar *username;
332
        gboolean audio_active;
333
        gboolean video_active;
334
        uint64_t bitrate;
335
        guint16 slowlink_count;
336
        struct janus_videocall_session *peer;
337
        volatile gint hangingup;
338
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
339
} janus_videocall_session;
340
static GHashTable *sessions;
341
static GList *old_sessions;
342
static janus_mutex sessions_mutex;
343

    
344

    
345
/* Error codes */
346
#define JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR                        499
347
#define JANUS_VIDEOCALL_ERROR_NO_MESSAGE                        470
348
#define JANUS_VIDEOCALL_ERROR_INVALID_JSON                        471
349
#define JANUS_VIDEOCALL_ERROR_INVALID_REQUEST                472
350
#define JANUS_VIDEOCALL_ERROR_REGISTER_FIRST                473
351
#define JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT                474
352
#define JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT                475
353
#define JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN                476
354
#define JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED        477
355
#define JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME                478
356
#define JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST                        479
357
#define JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL                480
358
#define JANUS_VIDEOCALL_ERROR_NO_CALL                                481
359
#define JANUS_VIDEOCALL_ERROR_MISSING_SDP                        482
360

    
361

    
362
/* VideoCall watchdog/garbage collector (sort of) */
363
void *janus_videocall_watchdog(void *data);
364
void *janus_videocall_watchdog(void *data) {
365
        JANUS_LOG(LOG_INFO, "VideoCall watchdog started\n");
366
        gint64 now = 0;
367
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
368
                janus_mutex_lock(&sessions_mutex);
369
                /* Iterate on all the sessions */
370
                now = janus_get_monotonic_time();
371
                if(old_sessions != NULL) {
372
                        GList *sl = old_sessions;
373
                        JANUS_LOG(LOG_HUGE, "Checking %d old VideoCall sessions...\n", g_list_length(old_sessions));
374
                        while(sl) {
375
                                janus_videocall_session *session = (janus_videocall_session *)sl->data;
376
                                if(!session) {
377
                                        sl = sl->next;
378
                                        continue;
379
                                }
380
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
381
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
382
                                        JANUS_LOG(LOG_VERB, "Freeing old VideoCall session\n");
383
                                        GList *rm = sl->next;
384
                                        old_sessions = g_list_delete_link(old_sessions, sl);
385
                                        sl = rm;
386
                                        session->handle = NULL;
387
                                        g_free(session);
388
                                        session = NULL;
389
                                        continue;
390
                                }
391
                                sl = sl->next;
392
                        }
393
                }
394
                janus_mutex_unlock(&sessions_mutex);
395
                g_usleep(500000);
396
        }
397
        JANUS_LOG(LOG_INFO, "VideoCall watchdog stopped\n");
398
        return NULL;
399
}
400

    
401

    
402
/* Plugin implementation */
403
int janus_videocall_init(janus_callbacks *callback, const char *config_path) {
404
        if(g_atomic_int_get(&stopping)) {
405
                /* Still stopping from before */
406
                return -1;
407
        }
408
        if(callback == NULL || config_path == NULL) {
409
                /* Invalid arguments */
410
                return -1;
411
        }
412

    
413
        /* Read configuration */
414
        char filename[255];
415
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOCALL_PACKAGE);
416
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
417
        janus_config *config = janus_config_parse(filename);
418
        if(config != NULL)
419
                janus_config_print(config);
420
        /* This plugin actually has nothing to configure... */
421
        janus_config_destroy(config);
422
        config = NULL;
423
        
424
        sessions = g_hash_table_new(g_str_hash, g_str_equal);
425
        janus_mutex_init(&sessions_mutex);
426
        messages = g_async_queue_new_full((GDestroyNotify) janus_videocall_message_free);
427
        /* This is the callback we'll need to invoke to contact the gateway */
428
        gateway = callback;
429

    
430
        g_atomic_int_set(&initialized, 1);
431

    
432
        GError *error = NULL;
433
        /* Start the sessions watchdog */
434
        watchdog = g_thread_try_new("vcall watchdog", &janus_videocall_watchdog, NULL, &error);
435
        if(error != NULL) {
436
                g_atomic_int_set(&initialized, 0);
437
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall watchdog thread...\n", error->code, error->message ? error->message : "??");
438
                return -1;
439
        }
440
        /* Launch the thread that will handle incoming messages */
441
        handler_thread = g_thread_try_new("janus videocall handler", janus_videocall_handler, NULL, &error);
442
        if(error != NULL) {
443
                g_atomic_int_set(&initialized, 0);
444
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall handler thread...\n", error->code, error->message ? error->message : "??");
445
                return -1;
446
        }
447
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOCALL_NAME);
448
        return 0;
449
}
450

    
451
void janus_videocall_destroy(void) {
452
        if(!g_atomic_int_get(&initialized))
453
                return;
454
        g_atomic_int_set(&stopping, 1);
455
        if(handler_thread != NULL) {
456
                g_thread_join(handler_thread);
457
                handler_thread = NULL;
458
        }
459
        if(watchdog != NULL) {
460
                g_thread_join(watchdog);
461
                watchdog = NULL;
462
        }
463
        /* FIXME We should destroy the sessions cleanly */
464
        janus_mutex_lock(&sessions_mutex);
465
        g_hash_table_destroy(sessions);
466
        janus_mutex_unlock(&sessions_mutex);
467
        g_async_queue_unref(messages);
468
        messages = NULL;
469
        sessions = NULL;
470
        g_atomic_int_set(&initialized, 0);
471
        g_atomic_int_set(&stopping, 0);
472
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOCALL_NAME);
473
}
474

    
475
int janus_videocall_get_api_compatibility(void) {
476
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
477
        return JANUS_PLUGIN_API_VERSION;
478
}
479

    
480
int janus_videocall_get_version(void) {
481
        return JANUS_VIDEOCALL_VERSION;
482
}
483

    
484
const char *janus_videocall_get_version_string(void) {
485
        return JANUS_VIDEOCALL_VERSION_STRING;
486
}
487

    
488
const char *janus_videocall_get_description(void) {
489
        return JANUS_VIDEOCALL_DESCRIPTION;
490
}
491

    
492
const char *janus_videocall_get_name(void) {
493
        return JANUS_VIDEOCALL_NAME;
494
}
495

    
496
const char *janus_videocall_get_author(void) {
497
        return JANUS_VIDEOCALL_AUTHOR;
498
}
499

    
500
const char *janus_videocall_get_package(void) {
501
        return JANUS_VIDEOCALL_PACKAGE;
502
}
503

    
504
void janus_videocall_create_session(janus_plugin_session *handle, int *error) {
505
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
506
                *error = -1;
507
                return;
508
        }        
509
        janus_videocall_session *session = (janus_videocall_session *)calloc(1, sizeof(janus_videocall_session));
510
        if(session == NULL) {
511
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
512
                *error = -2;
513
                return;
514
        }
515
        session->handle = handle;
516
        session->audio_active = TRUE;
517
        session->video_active = TRUE;
518
        session->bitrate = 0;        /* No limit */
519
        session->peer = NULL;
520
        session->username = NULL;
521
        session->destroyed = 0;
522
        g_atomic_int_set(&session->hangingup, 1);
523
        handle->plugin_handle = session;
524

    
525
        return;
526
}
527

    
528
void janus_videocall_destroy_session(janus_plugin_session *handle, int *error) {
529
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
530
                *error = -1;
531
                return;
532
        }
533
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; 
534
        if(!session) {
535
                JANUS_LOG(LOG_ERR, "No VideoCall session associated with this handle...\n");
536
                *error = -2;
537
                return;
538
        }
539
        janus_mutex_lock(&sessions_mutex);
540
        if(!session->destroyed) {
541
                JANUS_LOG(LOG_VERB, "Removing VideoCall user %s session...\n", session->username ? session->username : "'unknown'");
542
                janus_videocall_hangup_media(handle);
543
                session->destroyed = janus_get_monotonic_time();
544
                if(session->username != NULL) {
545
                        int res = g_hash_table_remove(sessions, (gpointer)session->username);
546
                        JANUS_LOG(LOG_VERB, "  -- Removed: %d\n", res);
547
                }
548
                /* Cleaning up and removing the session is done in a lazy way */
549
                old_sessions = g_list_append(old_sessions, session);
550
        }
551
        janus_mutex_unlock(&sessions_mutex);
552
        return;
553
}
554

    
555
char *janus_videocall_query_session(janus_plugin_session *handle) {
556
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
557
                return NULL;
558
        }        
559
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;
560
        if(!session) {
561
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
562
                return NULL;
563
        }
564
        /* Provide some generic info, e.g., if we're in a call and with whom */
565
        json_t *info = json_object();
566
        json_object_set_new(info, "state", json_string(session->peer ? "incall" : "idle"));
567
        json_object_set_new(info, "username", session->username ? json_string(session->username) : NULL);
568
        if(session->peer) {
569
                json_object_set_new(info, "peer", session->peer->username ? json_string(session->peer->username) : NULL);
570
                json_object_set_new(info, "audio_active", json_string(session->audio_active ? "true" : "false"));
571
                json_object_set_new(info, "video_active", json_string(session->video_active ? "true" : "false"));
572
                json_object_set_new(info, "bitrate", json_integer(session->bitrate));
573
                json_object_set_new(info, "slowlink_count", json_integer(session->slowlink_count));
574
        }
575
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
576
        char *info_text = json_dumps(info, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
577
        json_decref(info);
578
        return info_text;
579
}
580

    
581
struct janus_plugin_result *janus_videocall_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
582
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
583
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized");
584
        janus_videocall_message *msg = calloc(1, sizeof(janus_videocall_message));
585
        if(msg == NULL) {
586
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
587
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, "Memory error");
588
        }
589
        msg->handle = handle;
590
        msg->transaction = transaction;
591
        msg->message = message;
592
        msg->sdp_type = sdp_type;
593
        msg->sdp = sdp;
594
        g_async_queue_push(messages, msg);
595

    
596
        /* All the requests to this plugin are handled asynchronously */
597
        return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL);
598
}
599

    
600
void janus_videocall_setup_media(janus_plugin_session *handle) {
601
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
602
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
603
                return;
604
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
605
        if(!session) {
606
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
607
                return;
608
        }
609
        if(session->destroyed)
610
                return;
611
        g_atomic_int_set(&session->hangingup, 0);
612
        /* We really don't care, as we only relay RTP/RTCP we get in the first place anyway */
613
}
614

    
615
void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
616
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
617
                return;
618
        if(gateway) {
619
                /* Honour the audio/video active flags */
620
                janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
621
                if(!session) {
622
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
623
                        return;
624
                }
625
                if(!session->peer) {
626
                        JANUS_LOG(LOG_ERR, "Session has no peer...\n");
627
                        return;
628
                }
629
                if(session->destroyed || session->peer->destroyed)
630
                        return;
631
                if((!video && session->audio_active) || (video && session->video_active)) {
632
                        gateway->relay_rtp(session->peer->handle, video, buf, len);
633
                }
634
        }
635
}
636

    
637
void janus_videocall_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
638
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
639
                return;
640
        if(gateway) {
641
                janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
642
                if(!session) {
643
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
644
                        return;
645
                }
646
                if(!session->peer) {
647
                        JANUS_LOG(LOG_ERR, "Session has no peer...\n");
648
                        return;
649
                }
650
                if(session->destroyed || session->peer->destroyed)
651
                        return;
652
                if(session->bitrate > 0)
653
                        janus_rtcp_cap_remb(buf, len, session->bitrate);
654
                gateway->relay_rtcp(session->peer->handle, video, buf, len);
655
        }
656
}
657

    
658
void janus_videocall_incoming_data(janus_plugin_session *handle, char *buf, int len) {
659
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
660
                return;
661
        if(gateway) {
662
                janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
663
                if(!session) {
664
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
665
                        return;
666
                }
667
                if(!session->peer) {
668
                        JANUS_LOG(LOG_ERR, "Session has no peer...\n");
669
                        return;
670
                }
671
                if(session->destroyed || session->peer->destroyed)
672
                        return;
673
                if(buf == NULL || len <= 0)
674
                        return;
675
                char text[1<<16];
676
                memset(text, 0, 1<<16);
677
                memcpy(text, buf, len);
678
                text[len] = '\0';
679
                JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
680
                gateway->relay_data(session->peer->handle, text, strlen(text));
681
        }
682
}
683

    
684
void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video) {
685
        /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */
686
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
687
                return;
688
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
689
        if(!session) {
690
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
691
                return;
692
        }
693
        if(session->destroyed)
694
                return;
695
        session->slowlink_count++;
696
        if(uplink && !video && !session->audio_active) {
697
                /* We're not relaying audio and the peer is expecting it, so NACKs are normal */
698
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for audio, but that's expected, a configure disabled the audio forwarding\n");
699
        } else if(uplink && video && !session->video_active) {
700
                /* We're not relaying video and the peer is expecting it, so NACKs are normal */
701
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for video, but that's expected, a configure disabled the video forwarding\n");
702
        } else {
703
                /* Slow uplink or downlink, maybe we set the bitrate cap too high? */
704
                if(video) {
705
                        /* Halve the bitrate, but don't go too low... */
706
                        if(!uplink) {
707
                                /* Downlink issue, user has trouble sending, halve this user's bitrate cap */
708
                                session->bitrate = session->bitrate > 0 ? session->bitrate : 512*1024;
709
                                session->bitrate = session->bitrate/2;
710
                                if(session->bitrate < 64*1024)
711
                                        session->bitrate = 64*1024;
712
                        } else {
713
                                /* Uplink issue, user has trouble receiving, halve this user's peer's bitrate cap */
714
                                if(session->peer == NULL || session->peer->handle == NULL)
715
                                        return;        /* Nothing to do */
716
                                session->peer->bitrate = session->peer->bitrate > 0 ? session->peer->bitrate : 512*1024;
717
                                session->peer->bitrate = session->peer->bitrate/2;
718
                                if(session->peer->bitrate < 64*1024)
719
                                        session->peer->bitrate = 64*1024;
720
                        }
721
                        JANUS_LOG(LOG_WARN, "Getting a lot of NACKs (slow %s) for %s, forcing a lower REMB: %"SCNu64"\n",
722
                                uplink ? "uplink" : "downlink", video ? "video" : "audio", uplink ? session->peer->bitrate : session->bitrate);
723
                        /* ... and send a new REMB back */
724
                        char rtcpbuf[200];
725
                        memset(rtcpbuf, 0, 200);
726
                        /* FIXME First put a RR (fake)... */
727
                        int rrlen = 32;
728
                        rtcp_rr *rr = (rtcp_rr *)&rtcpbuf;
729
                        rr->header.version = 2;
730
                        rr->header.type = RTCP_RR;
731
                        rr->header.rc = 1;
732
                        rr->header.length = htons((rrlen/4)-1);
733
                        /* ... then put a SDES... */
734
                        int sdeslen = janus_rtcp_sdes((char *)(&rtcpbuf)+rrlen, 200-rrlen, "janusvideo", 10);
735
                        if(sdeslen > 0) {
736
                                /* ... and then finally a REMB */
737
                                janus_rtcp_remb((char *)(&rtcpbuf)+rrlen+sdeslen, 24, uplink ? session->peer->bitrate : session->bitrate);
738
                                gateway->relay_rtcp(uplink ? session->peer->handle : handle, 1, rtcpbuf, rrlen+sdeslen+24);
739
                        }
740
                        /* As a last thing, notify the affected user about this */
741
                        json_t *event = json_object();
742
                        json_object_set_new(event, "videocall", json_string("event"));
743
                        json_t *result = json_object();
744
                        json_object_set_new(result, "status", json_string("slow_link"));
745
                        json_object_set_new(result, "bitrate", json_integer(uplink ? session->peer->bitrate : session->bitrate));
746
                        json_object_set_new(event, "result", result);
747
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
748
                        json_decref(event);
749
                        json_decref(result);
750
                        event = NULL;
751
                        gateway->push_event(uplink ? session->peer->handle : handle, &janus_videocall_plugin, NULL, event_text, NULL, NULL);
752
                        g_free(event_text);
753
                }
754
        }
755
}
756

    
757
void janus_videocall_hangup_media(janus_plugin_session *handle) {
758
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
759
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
760
                return;
761
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
762
        if(!session) {
763
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
764
                return;
765
        }
766
        if(session->destroyed)
767
                return;
768
        if(g_atomic_int_add(&session->hangingup, 1))
769
                return;
770
        if(session->peer) {
771
                /* Send event to our peer too */
772
                json_t *call = json_object();
773
                json_object_set_new(call, "videocall", json_string("event"));
774
                json_t *calling = json_object();
775
                json_object_set_new(calling, "event", json_string("hangup"));
776
                json_object_set_new(calling, "username", json_string(session->username));
777
                json_object_set_new(calling, "reason", json_string("Remote hangup"));
778
                json_object_set_new(call, "result", calling);
779
                char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
780
                json_decref(call);
781
                JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
782
                int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL);
783
                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
784
                g_free(call_text);
785
        }
786
        session->peer = NULL;
787
        /* Reset controls */
788
        session->audio_active = TRUE;
789
        session->video_active = TRUE;
790
        session->bitrate = 0;
791
}
792

    
793
/* Thread to handle incoming messages */
794
static void *janus_videocall_handler(void *data) {
795
        JANUS_LOG(LOG_VERB, "Joining VideoCall handler thread\n");
796
        janus_videocall_message *msg = NULL;
797
        int error_code = 0;
798
        char *error_cause = calloc(512, sizeof(char));
799
        if(error_cause == NULL) {
800
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
801
                return NULL;
802
        }
803
        json_t *root = NULL;
804
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
805
                if(!messages || (msg = g_async_queue_try_pop(messages)) == NULL) {
806
                        usleep(50000);
807
                        continue;
808
                }
809
                janus_videocall_session *session = (janus_videocall_session *)msg->handle->plugin_handle;        
810
                if(!session) {
811
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
812
                        janus_videocall_message_free(msg);
813
                        continue;
814
                }
815
                if(session->destroyed) {
816
                        janus_videocall_message_free(msg);
817
                        continue;
818
                }
819
                /* Handle request */
820
                error_code = 0;
821
                root = NULL;
822
                JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message);
823
                if(msg->message == NULL) {
824
                        JANUS_LOG(LOG_ERR, "No message??\n");
825
                        error_code = JANUS_VIDEOCALL_ERROR_NO_MESSAGE;
826
                        g_snprintf(error_cause, 512, "%s", "No message??");
827
                        goto error;
828
                }
829
                json_error_t error;
830
                root = json_loads(msg->message, 0, &error);
831
                if(!root) {
832
                        JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
833
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON;
834
                        g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text);
835
                        goto error;
836
                }
837
                if(!json_is_object(root)) {
838
                        JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
839
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON;
840
                        g_snprintf(error_cause, 512, "JSON error: not an object");
841
                        goto error;
842
                }
843
                json_t *request = json_object_get(root, "request");
844
                if(!request) {
845
                        JANUS_LOG(LOG_ERR, "Missing element (request)\n");
846
                        error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT;
847
                        g_snprintf(error_cause, 512, "Missing element (request)");
848
                        goto error;
849
                }
850
                if(!json_is_string(request)) {
851
                        JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n");
852
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
853
                        g_snprintf(error_cause, 512, "Invalid element (request should be a string)");
854
                        goto error;
855
                }
856
                const char *request_text = json_string_value(request);
857
                json_t *result = NULL;
858
                char *sdp_type = NULL, *sdp = NULL;
859
                if(!strcasecmp(request_text, "list")) {
860
                        result = json_object();
861
                        json_t *list = json_array();
862
                        JANUS_LOG(LOG_VERB, "Request for the list of peers\n");
863
                        /* Return a list of all available mountpoints */
864
                        janus_mutex_lock(&sessions_mutex);
865
                        GHashTableIter iter;
866
                        gpointer value;
867
                        g_hash_table_iter_init(&iter, sessions);
868
                        while (g_hash_table_iter_next(&iter, NULL, &value)) {
869
                                janus_videocall_session *user = value;
870
                                if(user != NULL && user->username != NULL)
871
                                        json_array_append_new(list, json_string(user->username));
872
                        }
873
                        json_object_set_new(result, "list", list);
874
                        janus_mutex_unlock(&sessions_mutex);
875
                } else if(!strcasecmp(request_text, "register")) {
876
                        /* Map this handle to a username */
877
                        if(session->username != NULL) {
878
                                JANUS_LOG(LOG_ERR, "Already registered (%s)\n", session->username);
879
                                error_code = JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED;
880
                                g_snprintf(error_cause, 512, "Already registered (%s)", session->username);
881
                                goto error;
882
                        }
883
                        json_t *username = json_object_get(root, "username");
884
                        if(!username) {
885
                                JANUS_LOG(LOG_ERR, "Missing element (username)\n");
886
                                error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT;
887
                                g_snprintf(error_cause, 512, "Missing element (username)");
888
                                goto error;
889
                        }
890
                        if(!json_is_string(username)) {
891
                                JANUS_LOG(LOG_ERR, "Invalid element (username should be a string)\n");
892
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
893
                                g_snprintf(error_cause, 512, "Invalid element (username should be a string)");
894
                                goto error;
895
                        }
896
                        const char *username_text = json_string_value(username);
897
                        janus_mutex_lock(&sessions_mutex);
898
                        if(g_hash_table_lookup(sessions, username_text) != NULL) {
899
                                janus_mutex_unlock(&sessions_mutex);
900
                                JANUS_LOG(LOG_ERR, "Username '%s' already taken\n", username_text);
901
                                error_code = JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN;
902
                                g_snprintf(error_cause, 512, "Username '%s' already taken", username_text);
903
                                goto error;
904
                        }
905
                        janus_mutex_unlock(&sessions_mutex);
906
                        session->username = g_strdup(username_text);
907
                        if(session->username == NULL) {
908
                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
909
                                error_code = JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR;
910
                                g_snprintf(error_cause, 512, "Memory error");
911
                                goto error;
912
                        }
913
                        janus_mutex_lock(&sessions_mutex);
914
                        g_hash_table_insert(sessions, (gpointer)session->username, session);
915
                        janus_mutex_unlock(&sessions_mutex);
916
                        result = json_object();
917
                        json_object_set_new(result, "event", json_string("registered"));
918
                        json_object_set_new(result, "username", json_string(username_text));
919
                } else if(!strcasecmp(request_text, "call")) {
920
                        /* Call another peer */
921
                        if(session->username == NULL) {
922
                                JANUS_LOG(LOG_ERR, "Register a username first\n");
923
                                error_code = JANUS_VIDEOCALL_ERROR_REGISTER_FIRST;
924
                                g_snprintf(error_cause, 512, "Register a username first");
925
                                goto error;
926
                        }
927
                        if(session->peer != NULL) {
928
                                JANUS_LOG(LOG_ERR, "Already in a call\n");
929
                                error_code = JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL;
930
                                g_snprintf(error_cause, 512, "Already in a call");
931
                                goto error;
932
                        }
933
                        json_t *username = json_object_get(root, "username");
934
                        if(!username) {
935
                                JANUS_LOG(LOG_ERR, "Missing element (username)\n");
936
                                error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT;
937
                                g_snprintf(error_cause, 512, "Missing element (username)");
938
                                goto error;
939
                        }
940
                        if(!json_is_string(username)) {
941
                                JANUS_LOG(LOG_ERR, "Invalid element (username should be a string)\n");
942
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
943
                                g_snprintf(error_cause, 512, "Invalid element (username should be a string)");
944
                                goto error;
945
                        }
946
                        const char *username_text = json_string_value(username);
947
                        if(!strcmp(username_text, session->username)) {
948
                                JANUS_LOG(LOG_ERR, "You can't call yourself... use the EchoTest for that\n");
949
                                error_code = JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST;
950
                                g_snprintf(error_cause, 512, "You can't call yourself... use the EchoTest for that");
951
                                goto error;
952
                        }
953
                        janus_mutex_lock(&sessions_mutex);
954
                        janus_videocall_session *peer = g_hash_table_lookup(sessions, username_text);
955
                        if(peer == NULL || peer->destroyed) {
956
                                janus_mutex_unlock(&sessions_mutex);
957
                                JANUS_LOG(LOG_ERR, "Username '%s' doesn't exist\n", username_text);
958
                                error_code = JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME;
959
                                g_snprintf(error_cause, 512, "Username '%s' doesn't exist", username_text);
960
                                goto error;
961
                        }
962
                        if(peer->peer != NULL) {
963
                                janus_mutex_unlock(&sessions_mutex);
964
                                JANUS_LOG(LOG_VERB, "%s is busy\n", username_text);
965
                                result = json_object();
966
                                json_object_set_new(result, "event", json_string("hangup"));
967
                                json_object_set_new(result, "username", json_string(session->username));
968
                                json_object_set_new(result, "reason", json_string("User busy"));
969
                        } else {
970
                                janus_mutex_unlock(&sessions_mutex);
971
                                /* Any SDP to handle? if not, something's wrong */
972
                                if(!msg->sdp) {
973
                                        JANUS_LOG(LOG_ERR, "Missing SDP\n");
974
                                        error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP;
975
                                        g_snprintf(error_cause, 512, "Missing SDP");
976
                                        goto error;
977
                                }
978
                                janus_mutex_lock(&sessions_mutex);
979
                                session->peer = peer;
980
                                peer->peer = session;
981
                                janus_mutex_unlock(&sessions_mutex);
982
                                JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->username, session->peer->username);
983
                                JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
984
                                /* Send SDP to our peer */
985
                                json_t *call = json_object();
986
                                json_object_set_new(call, "videocall", json_string("event"));
987
                                json_t *calling = json_object();
988
                                json_object_set_new(calling, "event", json_string("incomingcall"));
989
                                json_object_set_new(calling, "username", json_string(session->username));
990
                                json_object_set_new(call, "result", calling);
991
                                char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
992
                                json_decref(call);
993
                                JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
994
                                int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, msg->sdp);
995
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
996
                                g_free(call_text);
997
                                /* Send an ack back */
998
                                result = json_object();
999
                                json_object_set_new(result, "event", json_string("calling"));
1000
                        }
1001
                } else if(!strcasecmp(request_text, "accept")) {
1002
                        /* Accept a call from another peer */
1003
                        if(session->peer == NULL) {
1004
                                JANUS_LOG(LOG_ERR, "No incoming call to accept\n");
1005
                                error_code = JANUS_VIDEOCALL_ERROR_NO_CALL;
1006
                                g_snprintf(error_cause, 512, "No incoming call to accept");
1007
                                goto error;
1008
                        }
1009
                        /* Any SDP to handle? if not, something's wrong */
1010
                        if(!msg->sdp) {
1011
                                JANUS_LOG(LOG_ERR, "Missing SDP\n");
1012
                                error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP;
1013
                                g_snprintf(error_cause, 512, "Missing SDP");
1014
                                goto error;
1015
                        }
1016
                        JANUS_LOG(LOG_VERB, "%s is accepting a call from %s\n", session->username, session->peer->username);
1017
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
1018
                        /* Send SDP to our peer */
1019
                        json_t *call = json_object();
1020
                        json_object_set_new(call, "videocall", json_string("event"));
1021
                        json_t *calling = json_object();
1022
                        json_object_set_new(calling, "event", json_string("accepted"));
1023
                        json_object_set_new(calling, "username", json_string(session->username));
1024
                        json_object_set_new(call, "result", calling);
1025
                        char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1026
                        json_decref(call);
1027
                        JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
1028
                        int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, msg->sdp);
1029
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1030
                        g_free(call_text);
1031
                        /* Send an ack back */
1032
                        result = json_object();
1033
                        json_object_set_new(result, "event", json_string("accepted"));
1034
                } else if(!strcasecmp(request_text, "set")) {
1035
                        /* Update the local configuration (audio/video mute/unmute, or bitrate cap) */
1036
                        json_t *audio = json_object_get(root, "audio");
1037
                        if(audio && !json_is_boolean(audio)) {
1038
                                JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
1039
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1040
                                g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
1041
                                goto error;
1042
                        }
1043
                        json_t *video = json_object_get(root, "video");
1044
                        if(video && !json_is_boolean(video)) {
1045
                                JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
1046
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1047
                                g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
1048
                                goto error;
1049
                        }
1050
                        json_t *bitrate = json_object_get(root, "bitrate");
1051
                        if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) {
1052
                                JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n");
1053
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1054
                                g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)");
1055
                                goto error;
1056
                        }
1057
                        if(audio) {
1058
                                session->audio_active = json_is_true(audio);
1059
                                JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false");
1060
                        }
1061
                        if(video) {
1062
                                if(!session->video_active && json_is_true(video)) {
1063
                                        /* Send a PLI */
1064
                                        JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n");
1065
                                        char buf[12];
1066
                                        memset(buf, 0, 12);
1067
                                        janus_rtcp_pli((char *)&buf, 12);
1068
                                        gateway->relay_rtcp(session->handle, 1, buf, 12);
1069
                                }
1070
                                session->video_active = json_is_true(video);
1071
                                JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false");
1072
                        }
1073
                        if(bitrate) {
1074
                                session->bitrate = json_integer_value(bitrate);
1075
                                JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate);
1076
                                if(session->bitrate > 0) {
1077
                                        /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */
1078
                                        char buf[24];
1079
                                        memset(buf, 0, 24);
1080
                                        janus_rtcp_remb((char *)&buf, 24, session->bitrate);
1081
                                        JANUS_LOG(LOG_VERB, "Sending REMB\n");
1082
                                        gateway->relay_rtcp(session->handle, 1, buf, 24);
1083
                                        /* FIXME How should we handle a subsequent "no limit" bitrate? */
1084
                                }
1085
                        }
1086
                        /* Send an ack back */
1087
                        result = json_object();
1088
                        json_object_set_new(result, "event", json_string("set"));
1089
                } else if(!strcasecmp(request_text, "hangup")) {
1090
                        /* Hangup an ongoing call or reject an incoming one */
1091
                        janus_mutex_lock(&sessions_mutex);
1092
                        janus_videocall_session *peer = session->peer;
1093
                        if(peer == NULL) {
1094
                                JANUS_LOG(LOG_WARN, "No call to hangup\n");
1095
                        } else {
1096
                                JANUS_LOG(LOG_VERB, "%s is hanging up the call with %s\n", session->username, peer->username);
1097
                                session->peer = NULL;
1098
                                peer->peer = NULL;
1099
                        }
1100
                        janus_mutex_unlock(&sessions_mutex);
1101
                        /* Notify the success as an hangup message */
1102
                        result = json_object();
1103
                        json_object_set_new(result, "event", json_string("hangup"));
1104
                        json_object_set_new(result, "username", json_string(session->username));
1105
                        json_object_set_new(result, "reason", json_string("We did the hangup"));
1106
                        if(peer != NULL) {
1107
                                /* Send event to our peer too */
1108
                                json_t *call = json_object();
1109
                                json_object_set_new(call, "videocall", json_string("event"));
1110
                                json_t *calling = json_object();
1111
                                json_object_set_new(calling, "event", json_string("hangup"));
1112
                                json_object_set_new(calling, "username", json_string(session->username));
1113
                                json_object_set_new(calling, "reason", json_string("Remote hangup"));
1114
                                json_object_set_new(call, "result", calling);
1115
                                char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1116
                                json_decref(call);
1117
                                JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
1118
                                int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL);
1119
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1120
                                g_free(call_text);
1121
                        }
1122
                } else {
1123
                        JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text);
1124
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_REQUEST;
1125
                        g_snprintf(error_cause, 512, "Unknown request (%s)", request_text);
1126
                        goto error;
1127
                }
1128

    
1129
                json_decref(root);
1130
                /* Prepare JSON event */
1131
                json_t *event = json_object();
1132
                json_object_set_new(event, "videocall", json_string("event"));
1133
                if(result != NULL)
1134
                        json_object_set_new(event, "result", result);
1135
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1136
                json_decref(event);
1137
                JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
1138
                int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, sdp_type, sdp);
1139
                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1140
                g_free(event_text);
1141
                if(sdp)
1142
                        g_free(sdp);
1143
                janus_videocall_message_free(msg);
1144
                continue;
1145
                
1146
error:
1147
                {
1148
                        if(root != NULL)
1149
                                json_decref(root);
1150
                        /* Prepare JSON error event */
1151
                        json_t *event = json_object();
1152
                        json_object_set_new(event, "videocall", json_string("event"));
1153
                        json_object_set_new(event, "error_code", json_integer(error_code));
1154
                        json_object_set_new(event, "error", json_string(error_cause));
1155
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1156
                        json_decref(event);
1157
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
1158
                        int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, NULL, NULL);
1159
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1160
                        g_free(event_text);
1161
                        janus_videocall_message_free(msg);
1162
                }
1163
        }
1164
        g_free(error_cause);
1165
        JANUS_LOG(LOG_VERB, "Leaving VideoCall handler thread\n");
1166
        return NULL;
1167
}