Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videocall.c @ a7bba2fb

History | View | Annotate | Download (50.2 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. All the attributes (except \c request) are
166
 * optional, so any request can contain a subset of them:
167
 *
168
\verbatim
169
{
170
        "request" : "set",
171
        "audio" : true|false,
172
        "video" : true|false,
173
        "bitrate" : <numeric bitrate value>,
174
        "record" : true|false,
175
        "filename" : <base path/filename to use for the recording>
176
}
177
\endverbatim
178
 *
179
 * \c audio instructs the plugin to do or do not relay audio frames;
180
 * \c video does the same for video; \c bitrate caps the bandwidth to
181
 * force on the browser encoding side (e.g., 128000 for 128kbps);
182
 * \c record enables or disables the recording of this peer; in case
183
 * recording is enabled, \c filename allows to specify a base
184
 * path/filename to use for the files (-audio.mjr and -video.mjr are
185
 * automatically appended). Beware that enabling the recording only
186
 * records this user's contribution, and not the whole call: to record
187
 * both sides, you need to enable recording for both the peers in the
188
 * call.
189
 * 
190
 * A successful request will result in a \c set event:
191
 * 
192
\verbatim
193
{
194
        "videocall" : "event",
195
        "result" : {
196
                "event" : "set"
197
        }
198
}
199
\endverbatim
200
 * 
201
 * To decline an incoming call, cancel an attempt to call or simply
202
 * hangup an ongoing conversation, the \c hangup request can be used,
203
 * which has to be formatted as follows:
204
 * 
205
\verbatim
206
{
207
        "request" : "hangup"
208
}
209
\endverbatim
210
 *
211
 * Whatever the reason of a call being closed (e.g., a \c hangup request,
212
 * a PeerConnection being closed, or something else), both parties in
213
 * the communication will receive a \c hangup event:
214
 * 
215
\verbatim
216
{
217
        "videocall" : "event",
218
        "result" : {
219
                "event" : "hangup",
220
                "username" : "<username of who closed the communication>",
221
                "reason" : "<description of what happened>"
222
        }
223
}
224
\endverbatim
225
 * 
226
 * \ingroup plugins
227
 * \ref plugins
228
 */
229

    
230
#include "plugin.h"
231

    
232
#include <jansson.h>
233

    
234
#include "../debug.h"
235
#include "../apierror.h"
236
#include "../config.h"
237
#include "../mutex.h"
238
#include "../record.h"
239
#include "../rtcp.h"
240
#include "../utils.h"
241

    
242

    
243
/* Plugin information */
244
#define JANUS_VIDEOCALL_VERSION                        5
245
#define JANUS_VIDEOCALL_VERSION_STRING        "0.0.5"
246
#define JANUS_VIDEOCALL_DESCRIPTION                "This is a simple video call plugin for Janus, allowing two WebRTC peers to call each other through the gateway."
247
#define JANUS_VIDEOCALL_NAME                        "JANUS VideoCall plugin"
248
#define JANUS_VIDEOCALL_AUTHOR                        "Meetecho s.r.l."
249
#define JANUS_VIDEOCALL_PACKAGE                        "janus.plugin.videocall"
250

    
251
/* Plugin methods */
252
janus_plugin *create(void);
253
int janus_videocall_init(janus_callbacks *callback, const char *config_path);
254
void janus_videocall_destroy(void);
255
int janus_videocall_get_api_compatibility(void);
256
int janus_videocall_get_version(void);
257
const char *janus_videocall_get_version_string(void);
258
const char *janus_videocall_get_description(void);
259
const char *janus_videocall_get_name(void);
260
const char *janus_videocall_get_author(void);
261
const char *janus_videocall_get_package(void);
262
void janus_videocall_create_session(janus_plugin_session *handle, int *error);
263
struct janus_plugin_result *janus_videocall_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp);
264
void janus_videocall_setup_media(janus_plugin_session *handle);
265
void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
266
void janus_videocall_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
267
void janus_videocall_incoming_data(janus_plugin_session *handle, char *buf, int len);
268
void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video);
269
void janus_videocall_hangup_media(janus_plugin_session *handle);
270
void janus_videocall_destroy_session(janus_plugin_session *handle, int *error);
271
char *janus_videocall_query_session(janus_plugin_session *handle);
272

    
273
/* Plugin setup */
274
static janus_plugin janus_videocall_plugin =
275
        JANUS_PLUGIN_INIT (
276
                .init = janus_videocall_init,
277
                .destroy = janus_videocall_destroy,
278

    
279
                .get_api_compatibility = janus_videocall_get_api_compatibility,
280
                .get_version = janus_videocall_get_version,
281
                .get_version_string = janus_videocall_get_version_string,
282
                .get_description = janus_videocall_get_description,
283
                .get_name = janus_videocall_get_name,
284
                .get_author = janus_videocall_get_author,
285
                .get_package = janus_videocall_get_package,
286
                
287
                .create_session = janus_videocall_create_session,
288
                .handle_message = janus_videocall_handle_message,
289
                .setup_media = janus_videocall_setup_media,
290
                .incoming_rtp = janus_videocall_incoming_rtp,
291
                .incoming_rtcp = janus_videocall_incoming_rtcp,
292
                .incoming_data = janus_videocall_incoming_data,
293
                .slow_link = janus_videocall_slow_link,
294
                .hangup_media = janus_videocall_hangup_media,
295
                .destroy_session = janus_videocall_destroy_session,
296
                .query_session = janus_videocall_query_session,
297
        );
298

    
299
/* Plugin creator */
300
janus_plugin *create(void) {
301
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOCALL_NAME);
302
        return &janus_videocall_plugin;
303
}
304

    
305

    
306
/* Useful stuff */
307
static volatile gint initialized = 0, stopping = 0;
308
static janus_callbacks *gateway = NULL;
309
static GThread *handler_thread;
310
static GThread *watchdog;
311
static void *janus_videocall_handler(void *data);
312

    
313
typedef struct janus_videocall_message {
314
        janus_plugin_session *handle;
315
        char *transaction;
316
        char *message;
317
        char *sdp_type;
318
        char *sdp;
319
} janus_videocall_message;
320
static GAsyncQueue *messages = NULL;
321
static janus_videocall_message exit_message;
322

    
323
static void janus_videocall_message_free(janus_videocall_message *msg) {
324
        if(!msg || msg == &exit_message)
325
                return;
326

    
327
        msg->handle = NULL;
328

    
329
        g_free(msg->transaction);
330
        msg->transaction = NULL;
331
        g_free(msg->message);
332
        msg->message = NULL;
333
        g_free(msg->sdp_type);
334
        msg->sdp_type = NULL;
335
        g_free(msg->sdp);
336
        msg->sdp = NULL;
337

    
338
        g_free(msg);
339
}
340

    
341
typedef struct janus_videocall_session {
342
        janus_plugin_session *handle;
343
        gchar *username;
344
        gboolean has_audio;
345
        gboolean has_video;
346
        gboolean audio_active;
347
        gboolean video_active;
348
        uint64_t bitrate;
349
        guint16 slowlink_count;
350
        struct janus_videocall_session *peer;
351
        janus_recorder *arc;        /* The Janus recorder instance for this user's audio, if enabled */
352
        janus_recorder *vrc;        /* The Janus recorder instance for this user's video, if enabled */
353
        volatile gint hangingup;
354
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
355
} janus_videocall_session;
356
static GHashTable *sessions;
357
static GList *old_sessions;
358
static janus_mutex sessions_mutex;
359

    
360

    
361
/* Error codes */
362
#define JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR                        499
363
#define JANUS_VIDEOCALL_ERROR_NO_MESSAGE                        470
364
#define JANUS_VIDEOCALL_ERROR_INVALID_JSON                        471
365
#define JANUS_VIDEOCALL_ERROR_INVALID_REQUEST                472
366
#define JANUS_VIDEOCALL_ERROR_REGISTER_FIRST                473
367
#define JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT                474
368
#define JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT                475
369
#define JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN                476
370
#define JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED        477
371
#define JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME                478
372
#define JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST                        479
373
#define JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL                480
374
#define JANUS_VIDEOCALL_ERROR_NO_CALL                                481
375
#define JANUS_VIDEOCALL_ERROR_MISSING_SDP                        482
376

    
377

    
378
/* VideoCall watchdog/garbage collector (sort of) */
379
void *janus_videocall_watchdog(void *data);
380
void *janus_videocall_watchdog(void *data) {
381
        JANUS_LOG(LOG_INFO, "VideoCall watchdog started\n");
382
        gint64 now = 0;
383
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
384
                janus_mutex_lock(&sessions_mutex);
385
                /* Iterate on all the sessions */
386
                now = janus_get_monotonic_time();
387
                if(old_sessions != NULL) {
388
                        GList *sl = old_sessions;
389
                        JANUS_LOG(LOG_HUGE, "Checking %d old VideoCall sessions...\n", g_list_length(old_sessions));
390
                        while(sl) {
391
                                janus_videocall_session *session = (janus_videocall_session *)sl->data;
392
                                if(!session) {
393
                                        sl = sl->next;
394
                                        continue;
395
                                }
396
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
397
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
398
                                        JANUS_LOG(LOG_VERB, "Freeing old VideoCall session\n");
399
                                        GList *rm = sl->next;
400
                                        old_sessions = g_list_delete_link(old_sessions, sl);
401
                                        sl = rm;
402
                                        session->handle = NULL;
403
                                        g_free(session);
404
                                        session = NULL;
405
                                        continue;
406
                                }
407
                                sl = sl->next;
408
                        }
409
                }
410
                janus_mutex_unlock(&sessions_mutex);
411
                g_usleep(500000);
412
        }
413
        JANUS_LOG(LOG_INFO, "VideoCall watchdog stopped\n");
414
        return NULL;
415
}
416

    
417

    
418
/* Plugin implementation */
419
int janus_videocall_init(janus_callbacks *callback, const char *config_path) {
420
        if(g_atomic_int_get(&stopping)) {
421
                /* Still stopping from before */
422
                return -1;
423
        }
424
        if(callback == NULL || config_path == NULL) {
425
                /* Invalid arguments */
426
                return -1;
427
        }
428

    
429
        /* Read configuration */
430
        char filename[255];
431
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOCALL_PACKAGE);
432
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
433
        janus_config *config = janus_config_parse(filename);
434
        if(config != NULL)
435
                janus_config_print(config);
436
        /* This plugin actually has nothing to configure... */
437
        janus_config_destroy(config);
438
        config = NULL;
439
        
440
        sessions = g_hash_table_new(g_str_hash, g_str_equal);
441
        janus_mutex_init(&sessions_mutex);
442
        messages = g_async_queue_new_full((GDestroyNotify) janus_videocall_message_free);
443
        /* This is the callback we'll need to invoke to contact the gateway */
444
        gateway = callback;
445

    
446
        g_atomic_int_set(&initialized, 1);
447

    
448
        GError *error = NULL;
449
        /* Start the sessions watchdog */
450
        watchdog = g_thread_try_new("vcall watchdog", &janus_videocall_watchdog, NULL, &error);
451
        if(error != NULL) {
452
                g_atomic_int_set(&initialized, 0);
453
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall watchdog thread...\n", error->code, error->message ? error->message : "??");
454
                return -1;
455
        }
456
        /* Launch the thread that will handle incoming messages */
457
        handler_thread = g_thread_try_new("janus videocall handler", janus_videocall_handler, NULL, &error);
458
        if(error != NULL) {
459
                g_atomic_int_set(&initialized, 0);
460
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall handler thread...\n", error->code, error->message ? error->message : "??");
461
                return -1;
462
        }
463
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOCALL_NAME);
464
        return 0;
465
}
466

    
467
void janus_videocall_destroy(void) {
468
        if(!g_atomic_int_get(&initialized))
469
                return;
470
        g_atomic_int_set(&stopping, 1);
471

    
472
        g_async_queue_push(messages, &exit_message);
473
        if(handler_thread != NULL) {
474
                g_thread_join(handler_thread);
475
                handler_thread = NULL;
476
        }
477
        if(watchdog != NULL) {
478
                g_thread_join(watchdog);
479
                watchdog = NULL;
480
        }
481
        /* FIXME We should destroy the sessions cleanly */
482
        janus_mutex_lock(&sessions_mutex);
483
        g_hash_table_destroy(sessions);
484
        janus_mutex_unlock(&sessions_mutex);
485
        g_async_queue_unref(messages);
486
        messages = NULL;
487
        sessions = NULL;
488
        g_atomic_int_set(&initialized, 0);
489
        g_atomic_int_set(&stopping, 0);
490
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOCALL_NAME);
491
}
492

    
493
int janus_videocall_get_api_compatibility(void) {
494
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
495
        return JANUS_PLUGIN_API_VERSION;
496
}
497

    
498
int janus_videocall_get_version(void) {
499
        return JANUS_VIDEOCALL_VERSION;
500
}
501

    
502
const char *janus_videocall_get_version_string(void) {
503
        return JANUS_VIDEOCALL_VERSION_STRING;
504
}
505

    
506
const char *janus_videocall_get_description(void) {
507
        return JANUS_VIDEOCALL_DESCRIPTION;
508
}
509

    
510
const char *janus_videocall_get_name(void) {
511
        return JANUS_VIDEOCALL_NAME;
512
}
513

    
514
const char *janus_videocall_get_author(void) {
515
        return JANUS_VIDEOCALL_AUTHOR;
516
}
517

    
518
const char *janus_videocall_get_package(void) {
519
        return JANUS_VIDEOCALL_PACKAGE;
520
}
521

    
522
void janus_videocall_create_session(janus_plugin_session *handle, int *error) {
523
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
524
                *error = -1;
525
                return;
526
        }        
527
        janus_videocall_session *session = (janus_videocall_session *)g_malloc0(sizeof(janus_videocall_session));
528
        if(session == NULL) {
529
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
530
                *error = -2;
531
                return;
532
        }
533
        session->handle = handle;
534
        session->has_audio = FALSE;
535
        session->has_video = FALSE;
536
        session->audio_active = TRUE;
537
        session->video_active = TRUE;
538
        session->bitrate = 0;        /* No limit */
539
        session->peer = NULL;
540
        session->username = NULL;
541
        session->destroyed = 0;
542
        g_atomic_int_set(&session->hangingup, 0);
543
        handle->plugin_handle = session;
544

    
545
        return;
546
}
547

    
548
void janus_videocall_destroy_session(janus_plugin_session *handle, int *error) {
549
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
550
                *error = -1;
551
                return;
552
        }
553
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; 
554
        if(!session) {
555
                JANUS_LOG(LOG_ERR, "No VideoCall session associated with this handle...\n");
556
                *error = -2;
557
                return;
558
        }
559
        janus_mutex_lock(&sessions_mutex);
560
        if(!session->destroyed) {
561
                JANUS_LOG(LOG_VERB, "Removing VideoCall user %s session...\n", session->username ? session->username : "'unknown'");
562
                janus_videocall_hangup_media(handle);
563
                session->destroyed = janus_get_monotonic_time();
564
                if(session->username != NULL) {
565
                        int res = g_hash_table_remove(sessions, (gpointer)session->username);
566
                        JANUS_LOG(LOG_VERB, "  -- Removed: %d\n", res);
567
                }
568
                /* Cleaning up and removing the session is done in a lazy way */
569
                old_sessions = g_list_append(old_sessions, session);
570
        }
571
        janus_mutex_unlock(&sessions_mutex);
572
        return;
573
}
574

    
575
char *janus_videocall_query_session(janus_plugin_session *handle) {
576
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
577
                return NULL;
578
        }        
579
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;
580
        if(!session) {
581
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
582
                return NULL;
583
        }
584
        /* Provide some generic info, e.g., if we're in a call and with whom */
585
        json_t *info = json_object();
586
        json_object_set_new(info, "state", json_string(session->peer ? "incall" : "idle"));
587
        json_object_set_new(info, "username", session->username ? json_string(session->username) : NULL);
588
        if(session->peer) {
589
                json_object_set_new(info, "peer", session->peer->username ? json_string(session->peer->username) : NULL);
590
                json_object_set_new(info, "audio_active", json_string(session->audio_active ? "true" : "false"));
591
                json_object_set_new(info, "video_active", json_string(session->video_active ? "true" : "false"));
592
                json_object_set_new(info, "bitrate", json_integer(session->bitrate));
593
                json_object_set_new(info, "slowlink_count", json_integer(session->slowlink_count));
594
        }
595
        if(session->arc || session->vrc) {
596
                json_t *recording = json_object();
597
                if(session->arc && session->arc->filename)
598
                        json_object_set_new(recording, "audio", json_string(session->arc->filename));
599
                if(session->vrc && session->vrc->filename)
600
                        json_object_set_new(recording, "video", json_string(session->vrc->filename));
601
                json_object_set_new(info, "recording", recording);
602
        }
603
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
604
        char *info_text = json_dumps(info, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
605
        json_decref(info);
606
        return info_text;
607
}
608

    
609
struct janus_plugin_result *janus_videocall_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
610
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
611
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized");
612
        janus_videocall_message *msg = g_malloc0(sizeof(janus_videocall_message));
613
        if(msg == NULL) {
614
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
615
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, "Memory error");
616
        }
617
        msg->handle = handle;
618
        msg->transaction = transaction;
619
        msg->message = message;
620
        msg->sdp_type = sdp_type;
621
        msg->sdp = sdp;
622
        g_async_queue_push(messages, msg);
623

    
624
        /* All the requests to this plugin are handled asynchronously */
625
        return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL);
626
}
627

    
628
void janus_videocall_setup_media(janus_plugin_session *handle) {
629
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
630
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
631
                return;
632
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
633
        if(!session) {
634
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
635
                return;
636
        }
637
        if(session->destroyed)
638
                return;
639
        g_atomic_int_set(&session->hangingup, 0);
640
        /* We really don't care, as we only relay RTP/RTCP we get in the first place anyway */
641
}
642

    
643
void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
644
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
645
                return;
646
        if(gateway) {
647
                /* Honour the audio/video active flags */
648
                janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
649
                if(!session) {
650
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
651
                        return;
652
                }
653
                if(!session->peer) {
654
                        JANUS_LOG(LOG_ERR, "Session has no peer...\n");
655
                        return;
656
                }
657
                if(session->destroyed || session->peer->destroyed)
658
                        return;
659
                if((!video && session->audio_active) || (video && session->video_active)) {
660
                        /* Save the frame if we're recording */
661
                        if(video && session->vrc)
662
                                janus_recorder_save_frame(session->vrc, buf, len);
663
                        else if(!video && session->arc)
664
                                janus_recorder_save_frame(session->arc, buf, len);
665
                        /* Forward the packet to the peer */
666
                        gateway->relay_rtp(session->peer->handle, video, buf, len);
667
                }
668
        }
669
}
670

    
671
void janus_videocall_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
672
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
673
                return;
674
        if(gateway) {
675
                janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
676
                if(!session) {
677
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
678
                        return;
679
                }
680
                if(!session->peer) {
681
                        JANUS_LOG(LOG_ERR, "Session has no peer...\n");
682
                        return;
683
                }
684
                if(session->destroyed || session->peer->destroyed)
685
                        return;
686
                if(session->bitrate > 0)
687
                        janus_rtcp_cap_remb(buf, len, session->bitrate);
688
                gateway->relay_rtcp(session->peer->handle, video, buf, len);
689
        }
690
}
691

    
692
void janus_videocall_incoming_data(janus_plugin_session *handle, char *buf, int len) {
693
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
694
                return;
695
        if(gateway) {
696
                janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
697
                if(!session) {
698
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
699
                        return;
700
                }
701
                if(!session->peer) {
702
                        JANUS_LOG(LOG_ERR, "Session has no peer...\n");
703
                        return;
704
                }
705
                if(session->destroyed || session->peer->destroyed)
706
                        return;
707
                if(buf == NULL || len <= 0)
708
                        return;
709
                char *text = g_malloc0(len+1);
710
                memcpy(text, buf, len);
711
                *(text+len) = '\0';
712
                JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
713
                gateway->relay_data(session->peer->handle, text, strlen(text));
714
                g_free(text);
715
        }
716
}
717

    
718
void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video) {
719
        /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */
720
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
721
                return;
722
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
723
        if(!session) {
724
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
725
                return;
726
        }
727
        if(session->destroyed)
728
                return;
729
        session->slowlink_count++;
730
        if(uplink && !video && !session->audio_active) {
731
                /* We're not relaying audio and the peer is expecting it, so NACKs are normal */
732
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for audio, but that's expected, a configure disabled the audio forwarding\n");
733
        } else if(uplink && video && !session->video_active) {
734
                /* We're not relaying video and the peer is expecting it, so NACKs are normal */
735
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for video, but that's expected, a configure disabled the video forwarding\n");
736
        } else {
737
                /* Slow uplink or downlink, maybe we set the bitrate cap too high? */
738
                if(video) {
739
                        /* Halve the bitrate, but don't go too low... */
740
                        if(!uplink) {
741
                                /* Downlink issue, user has trouble sending, halve this user's bitrate cap */
742
                                session->bitrate = session->bitrate > 0 ? session->bitrate : 512*1024;
743
                                session->bitrate = session->bitrate/2;
744
                                if(session->bitrate < 64*1024)
745
                                        session->bitrate = 64*1024;
746
                        } else {
747
                                /* Uplink issue, user has trouble receiving, halve this user's peer's bitrate cap */
748
                                if(session->peer == NULL || session->peer->handle == NULL)
749
                                        return;        /* Nothing to do */
750
                                session->peer->bitrate = session->peer->bitrate > 0 ? session->peer->bitrate : 512*1024;
751
                                session->peer->bitrate = session->peer->bitrate/2;
752
                                if(session->peer->bitrate < 64*1024)
753
                                        session->peer->bitrate = 64*1024;
754
                        }
755
                        JANUS_LOG(LOG_WARN, "Getting a lot of NACKs (slow %s) for %s, forcing a lower REMB: %"SCNu64"\n",
756
                                uplink ? "uplink" : "downlink", video ? "video" : "audio", uplink ? session->peer->bitrate : session->bitrate);
757
                        /* ... and send a new REMB back */
758
                        char rtcpbuf[200];
759
                        memset(rtcpbuf, 0, 200);
760
                        /* FIXME First put a RR (fake)... */
761
                        int rrlen = 32;
762
                        rtcp_rr *rr = (rtcp_rr *)&rtcpbuf;
763
                        rr->header.version = 2;
764
                        rr->header.type = RTCP_RR;
765
                        rr->header.rc = 1;
766
                        rr->header.length = htons((rrlen/4)-1);
767
                        /* ... then put a SDES... */
768
                        int sdeslen = janus_rtcp_sdes((char *)(&rtcpbuf)+rrlen, 200-rrlen, "janusvideo", 10);
769
                        if(sdeslen > 0) {
770
                                /* ... and then finally a REMB */
771
                                janus_rtcp_remb((char *)(&rtcpbuf)+rrlen+sdeslen, 24, uplink ? session->peer->bitrate : session->bitrate);
772
                                gateway->relay_rtcp(uplink ? session->peer->handle : handle, 1, rtcpbuf, rrlen+sdeslen+24);
773
                        }
774
                        /* As a last thing, notify the affected user about this */
775
                        json_t *event = json_object();
776
                        json_object_set_new(event, "videocall", json_string("event"));
777
                        json_t *result = json_object();
778
                        json_object_set_new(result, "status", json_string("slow_link"));
779
                        json_object_set_new(result, "bitrate", json_integer(uplink ? session->peer->bitrate : session->bitrate));
780
                        json_object_set_new(event, "result", result);
781
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
782
                        json_decref(event);
783
                        json_decref(result);
784
                        event = NULL;
785
                        gateway->push_event(uplink ? session->peer->handle : handle, &janus_videocall_plugin, NULL, event_text, NULL, NULL);
786
                        g_free(event_text);
787
                }
788
        }
789
}
790

    
791
void janus_videocall_hangup_media(janus_plugin_session *handle) {
792
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
793
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
794
                return;
795
        janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle;        
796
        if(!session) {
797
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
798
                return;
799
        }
800
        if(session->destroyed)
801
                return;
802
        if(g_atomic_int_add(&session->hangingup, 1))
803
                return;
804
        /* Get rid of the recorders, if available */
805
        if(session->arc) {
806
                janus_recorder_close(session->arc);
807
                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
808
                janus_recorder_free(session->arc);
809
        }
810
        session->arc = NULL;
811
        if(session->vrc) {
812
                janus_recorder_close(session->vrc);
813
                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
814
                janus_recorder_free(session->vrc);
815
        }
816
        session->vrc = NULL;
817
        if(session->peer) {
818
                /* Send event to our peer too */
819
                json_t *call = json_object();
820
                json_object_set_new(call, "videocall", json_string("event"));
821
                json_t *calling = json_object();
822
                json_object_set_new(calling, "event", json_string("hangup"));
823
                json_object_set_new(calling, "username", json_string(session->username));
824
                json_object_set_new(calling, "reason", json_string("Remote hangup"));
825
                json_object_set_new(call, "result", calling);
826
                char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
827
                json_decref(call);
828
                JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
829
                int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL);
830
                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
831
                g_free(call_text);
832
        }
833
        session->peer = NULL;
834
        /* Reset controls */
835
        session->has_audio = FALSE;
836
        session->has_video = FALSE;
837
        session->audio_active = TRUE;
838
        session->video_active = TRUE;
839
        session->bitrate = 0;
840
}
841

    
842
/* Thread to handle incoming messages */
843
static void *janus_videocall_handler(void *data) {
844
        JANUS_LOG(LOG_VERB, "Joining VideoCall handler thread\n");
845
        janus_videocall_message *msg = NULL;
846
        int error_code = 0;
847
        char *error_cause = g_malloc0(512);
848
        if(error_cause == NULL) {
849
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
850
                return NULL;
851
        }
852
        json_t *root = NULL;
853
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
854
                msg = g_async_queue_pop(messages);
855
                if(msg == NULL)
856
                        continue;
857
                if(msg == &exit_message)
858
                        break;
859
                if(msg->handle == NULL) {
860
                        janus_videocall_message_free(msg);
861
                        continue;
862
                }
863
                janus_videocall_session *session = (janus_videocall_session *)msg->handle->plugin_handle;
864
                if(!session) {
865
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
866
                        janus_videocall_message_free(msg);
867
                        continue;
868
                }
869
                if(session->destroyed) {
870
                        janus_videocall_message_free(msg);
871
                        continue;
872
                }
873
                /* Handle request */
874
                error_code = 0;
875
                root = NULL;
876
                JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message);
877
                if(msg->message == NULL) {
878
                        JANUS_LOG(LOG_ERR, "No message??\n");
879
                        error_code = JANUS_VIDEOCALL_ERROR_NO_MESSAGE;
880
                        g_snprintf(error_cause, 512, "%s", "No message??");
881
                        goto error;
882
                }
883
                json_error_t error;
884
                root = json_loads(msg->message, 0, &error);
885
                if(!root) {
886
                        JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
887
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON;
888
                        g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text);
889
                        goto error;
890
                }
891
                if(!json_is_object(root)) {
892
                        JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
893
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON;
894
                        g_snprintf(error_cause, 512, "JSON error: not an object");
895
                        goto error;
896
                }
897
                json_t *request = json_object_get(root, "request");
898
                if(!request) {
899
                        JANUS_LOG(LOG_ERR, "Missing element (request)\n");
900
                        error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT;
901
                        g_snprintf(error_cause, 512, "Missing element (request)");
902
                        goto error;
903
                }
904
                if(!json_is_string(request)) {
905
                        JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n");
906
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
907
                        g_snprintf(error_cause, 512, "Invalid element (request should be a string)");
908
                        goto error;
909
                }
910
                const char *request_text = json_string_value(request);
911
                json_t *result = NULL;
912
                char *sdp_type = NULL, *sdp = NULL;
913
                if(!strcasecmp(request_text, "list")) {
914
                        result = json_object();
915
                        json_t *list = json_array();
916
                        JANUS_LOG(LOG_VERB, "Request for the list of peers\n");
917
                        /* Return a list of all available mountpoints */
918
                        janus_mutex_lock(&sessions_mutex);
919
                        GHashTableIter iter;
920
                        gpointer value;
921
                        g_hash_table_iter_init(&iter, sessions);
922
                        while (g_hash_table_iter_next(&iter, NULL, &value)) {
923
                                janus_videocall_session *user = value;
924
                                if(user != NULL && user->username != NULL)
925
                                        json_array_append_new(list, json_string(user->username));
926
                        }
927
                        json_object_set_new(result, "list", list);
928
                        janus_mutex_unlock(&sessions_mutex);
929
                } else if(!strcasecmp(request_text, "register")) {
930
                        /* Map this handle to a username */
931
                        if(session->username != NULL) {
932
                                JANUS_LOG(LOG_ERR, "Already registered (%s)\n", session->username);
933
                                error_code = JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED;
934
                                g_snprintf(error_cause, 512, "Already registered (%s)", session->username);
935
                                goto error;
936
                        }
937
                        json_t *username = json_object_get(root, "username");
938
                        if(!username) {
939
                                JANUS_LOG(LOG_ERR, "Missing element (username)\n");
940
                                error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT;
941
                                g_snprintf(error_cause, 512, "Missing element (username)");
942
                                goto error;
943
                        }
944
                        if(!json_is_string(username)) {
945
                                JANUS_LOG(LOG_ERR, "Invalid element (username should be a string)\n");
946
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
947
                                g_snprintf(error_cause, 512, "Invalid element (username should be a string)");
948
                                goto error;
949
                        }
950
                        const char *username_text = json_string_value(username);
951
                        janus_mutex_lock(&sessions_mutex);
952
                        if(g_hash_table_lookup(sessions, username_text) != NULL) {
953
                                janus_mutex_unlock(&sessions_mutex);
954
                                JANUS_LOG(LOG_ERR, "Username '%s' already taken\n", username_text);
955
                                error_code = JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN;
956
                                g_snprintf(error_cause, 512, "Username '%s' already taken", username_text);
957
                                goto error;
958
                        }
959
                        janus_mutex_unlock(&sessions_mutex);
960
                        session->username = g_strdup(username_text);
961
                        if(session->username == NULL) {
962
                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
963
                                error_code = JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR;
964
                                g_snprintf(error_cause, 512, "Memory error");
965
                                goto error;
966
                        }
967
                        janus_mutex_lock(&sessions_mutex);
968
                        g_hash_table_insert(sessions, (gpointer)session->username, session);
969
                        janus_mutex_unlock(&sessions_mutex);
970
                        result = json_object();
971
                        json_object_set_new(result, "event", json_string("registered"));
972
                        json_object_set_new(result, "username", json_string(username_text));
973
                } else if(!strcasecmp(request_text, "call")) {
974
                        /* Call another peer */
975
                        if(session->username == NULL) {
976
                                JANUS_LOG(LOG_ERR, "Register a username first\n");
977
                                error_code = JANUS_VIDEOCALL_ERROR_REGISTER_FIRST;
978
                                g_snprintf(error_cause, 512, "Register a username first");
979
                                goto error;
980
                        }
981
                        if(session->peer != NULL) {
982
                                JANUS_LOG(LOG_ERR, "Already in a call\n");
983
                                error_code = JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL;
984
                                g_snprintf(error_cause, 512, "Already in a call");
985
                                goto error;
986
                        }
987
                        json_t *username = json_object_get(root, "username");
988
                        if(!username) {
989
                                JANUS_LOG(LOG_ERR, "Missing element (username)\n");
990
                                error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT;
991
                                g_snprintf(error_cause, 512, "Missing element (username)");
992
                                goto error;
993
                        }
994
                        if(!json_is_string(username)) {
995
                                JANUS_LOG(LOG_ERR, "Invalid element (username should be a string)\n");
996
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
997
                                g_snprintf(error_cause, 512, "Invalid element (username should be a string)");
998
                                goto error;
999
                        }
1000
                        const char *username_text = json_string_value(username);
1001
                        if(!strcmp(username_text, session->username)) {
1002
                                JANUS_LOG(LOG_ERR, "You can't call yourself... use the EchoTest for that\n");
1003
                                error_code = JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST;
1004
                                g_snprintf(error_cause, 512, "You can't call yourself... use the EchoTest for that");
1005
                                goto error;
1006
                        }
1007
                        janus_mutex_lock(&sessions_mutex);
1008
                        janus_videocall_session *peer = g_hash_table_lookup(sessions, username_text);
1009
                        if(peer == NULL || peer->destroyed) {
1010
                                janus_mutex_unlock(&sessions_mutex);
1011
                                JANUS_LOG(LOG_ERR, "Username '%s' doesn't exist\n", username_text);
1012
                                error_code = JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME;
1013
                                g_snprintf(error_cause, 512, "Username '%s' doesn't exist", username_text);
1014
                                goto error;
1015
                        }
1016
                        if(peer->peer != NULL) {
1017
                                janus_mutex_unlock(&sessions_mutex);
1018
                                JANUS_LOG(LOG_VERB, "%s is busy\n", username_text);
1019
                                result = json_object();
1020
                                json_object_set_new(result, "event", json_string("hangup"));
1021
                                json_object_set_new(result, "username", json_string(session->username));
1022
                                json_object_set_new(result, "reason", json_string("User busy"));
1023
                        } else {
1024
                                janus_mutex_unlock(&sessions_mutex);
1025
                                /* Any SDP to handle? if not, something's wrong */
1026
                                if(!msg->sdp) {
1027
                                        JANUS_LOG(LOG_ERR, "Missing SDP\n");
1028
                                        error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP;
1029
                                        g_snprintf(error_cause, 512, "Missing SDP");
1030
                                        goto error;
1031
                                }
1032
                                janus_mutex_lock(&sessions_mutex);
1033
                                session->peer = peer;
1034
                                peer->peer = session;
1035
                                session->has_audio = (strstr(msg->sdp, "m=audio") != NULL);
1036
                                session->has_video = (strstr(msg->sdp, "m=video") != NULL);
1037
                                janus_mutex_unlock(&sessions_mutex);
1038
                                JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->username, session->peer->username);
1039
                                JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
1040
                                /* Send SDP to our peer */
1041
                                json_t *call = json_object();
1042
                                json_object_set_new(call, "videocall", json_string("event"));
1043
                                json_t *calling = json_object();
1044
                                json_object_set_new(calling, "event", json_string("incomingcall"));
1045
                                json_object_set_new(calling, "username", json_string(session->username));
1046
                                json_object_set_new(call, "result", calling);
1047
                                char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1048
                                json_decref(call);
1049
                                /* Make also sure we get rid of ULPfec, red, etc. */
1050
                                char *sdp = g_strdup(msg->sdp);
1051
                                if(strstr(sdp, "ulpfec")) {
1052
                                        /* FIXME This really needs some better code */
1053
                                        sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", "");
1054
                                        sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", "");
1055
                                        sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", "");
1056
                                        sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", "");
1057
                                        sdp = janus_string_replace(sdp, "a=rtpmap:97 rtx/90000\r\n", "");
1058
                                        sdp = janus_string_replace(sdp, "a=fmtp:97 apt=101\r\n", "");
1059
                                        sdp = janus_string_replace(sdp, "a=rtpmap:98 rtx/90000\r\n", "");
1060
                                        sdp = janus_string_replace(sdp, "a=fmtp:98 apt=116\r\n", "");
1061
                                        sdp = janus_string_replace(sdp, " 116", "");
1062
                                        sdp = janus_string_replace(sdp, " 117", "");
1063
                                        sdp = janus_string_replace(sdp, " 96", "");
1064
                                        sdp = janus_string_replace(sdp, " 97", "");
1065
                                        sdp = janus_string_replace(sdp, " 98", "");
1066
                                }
1067
                                g_atomic_int_set(&session->hangingup, 0);
1068
                                JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
1069
                                int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, sdp);
1070
                                g_free(sdp);
1071
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1072
                                g_free(call_text);
1073
                                /* Send an ack back */
1074
                                result = json_object();
1075
                                json_object_set_new(result, "event", json_string("calling"));
1076
                        }
1077
                } else if(!strcasecmp(request_text, "accept")) {
1078
                        /* Accept a call from another peer */
1079
                        if(session->peer == NULL) {
1080
                                JANUS_LOG(LOG_ERR, "No incoming call to accept\n");
1081
                                error_code = JANUS_VIDEOCALL_ERROR_NO_CALL;
1082
                                g_snprintf(error_cause, 512, "No incoming call to accept");
1083
                                goto error;
1084
                        }
1085
                        /* Any SDP to handle? if not, something's wrong */
1086
                        if(!msg->sdp) {
1087
                                JANUS_LOG(LOG_ERR, "Missing SDP\n");
1088
                                error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP;
1089
                                g_snprintf(error_cause, 512, "Missing SDP");
1090
                                goto error;
1091
                        }
1092
                        JANUS_LOG(LOG_VERB, "%s is accepting a call from %s\n", session->username, session->peer->username);
1093
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
1094
                        session->has_audio = (strstr(msg->sdp, "m=audio") != NULL);
1095
                        session->has_video = (strstr(msg->sdp, "m=video") != NULL);
1096
                        /* Send SDP to our peer */
1097
                        json_t *call = json_object();
1098
                        json_object_set_new(call, "videocall", json_string("event"));
1099
                        json_t *calling = json_object();
1100
                        json_object_set_new(calling, "event", json_string("accepted"));
1101
                        json_object_set_new(calling, "username", json_string(session->username));
1102
                        json_object_set_new(call, "result", calling);
1103
                        char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1104
                        json_decref(call);
1105
                        g_atomic_int_set(&session->hangingup, 0);
1106
                        JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
1107
                        int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, msg->sdp);
1108
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1109
                        g_free(call_text);
1110
                        /* Send an ack back */
1111
                        result = json_object();
1112
                        json_object_set_new(result, "event", json_string("accepted"));
1113
                } else if(!strcasecmp(request_text, "set")) {
1114
                        /* Update the local configuration (audio/video mute/unmute, bitrate cap or recording) */
1115
                        json_t *audio = json_object_get(root, "audio");
1116
                        if(audio && !json_is_boolean(audio)) {
1117
                                JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
1118
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1119
                                g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
1120
                                goto error;
1121
                        }
1122
                        json_t *video = json_object_get(root, "video");
1123
                        if(video && !json_is_boolean(video)) {
1124
                                JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
1125
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1126
                                g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
1127
                                goto error;
1128
                        }
1129
                        json_t *bitrate = json_object_get(root, "bitrate");
1130
                        if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) {
1131
                                JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n");
1132
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1133
                                g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)");
1134
                                goto error;
1135
                        }
1136
                        json_t *record = json_object_get(root, "record");
1137
                        if(record && !json_is_boolean(record)) {
1138
                                JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n");
1139
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1140
                                g_snprintf(error_cause, 512, "Invalid value (record should be a boolean)");
1141
                                goto error;
1142
                        }
1143
                        json_t *recfile = json_object_get(root, "filename");
1144
                        if(recfile && !json_is_string(recfile)) {
1145
                                JANUS_LOG(LOG_ERR, "Invalid element (filename should be a string)\n");
1146
                                error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT;
1147
                                g_snprintf(error_cause, 512, "Invalid value (filename should be a string)");
1148
                                goto error;
1149
                        }
1150
                        if(audio) {
1151
                                session->audio_active = json_is_true(audio);
1152
                                JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false");
1153
                        }
1154
                        if(video) {
1155
                                if(!session->video_active && json_is_true(video)) {
1156
                                        /* Send a PLI */
1157
                                        JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n");
1158
                                        char buf[12];
1159
                                        memset(buf, 0, 12);
1160
                                        janus_rtcp_pli((char *)&buf, 12);
1161
                                        gateway->relay_rtcp(session->handle, 1, buf, 12);
1162
                                }
1163
                                session->video_active = json_is_true(video);
1164
                                JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false");
1165
                        }
1166
                        if(bitrate) {
1167
                                session->bitrate = json_integer_value(bitrate);
1168
                                JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate);
1169
                                if(session->bitrate > 0) {
1170
                                        /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */
1171
                                        char buf[24];
1172
                                        memset(buf, 0, 24);
1173
                                        janus_rtcp_remb((char *)&buf, 24, session->bitrate);
1174
                                        JANUS_LOG(LOG_VERB, "Sending REMB\n");
1175
                                        gateway->relay_rtcp(session->handle, 1, buf, 24);
1176
                                        /* FIXME How should we handle a subsequent "no limit" bitrate? */
1177
                                }
1178
                        }
1179
                        if(record) {
1180
                                if(msg->sdp) {
1181
                                        session->has_audio = (strstr(msg->sdp, "m=audio") != NULL);
1182
                                        session->has_video = (strstr(msg->sdp, "m=video") != NULL);
1183
                                }
1184
                                gboolean recording = json_is_true(record);
1185
                                const char *recording_base = json_string_value(recfile);
1186
                                JANUS_LOG(LOG_VERB, "Recording %s (base filename: %s)\n", recording ? "enabled" : "disabled", recording_base ? recording_base : "not provided");
1187
                                if(!recording) {
1188
                                        /* Not recording (anymore?) */
1189
                                        if(session->arc) {
1190
                                                janus_recorder_close(session->arc);
1191
                                                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
1192
                                                janus_recorder_free(session->arc);
1193
                                        }
1194
                                        session->arc = NULL;
1195
                                        if(session->vrc) {
1196
                                                janus_recorder_close(session->vrc);
1197
                                                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
1198
                                                janus_recorder_free(session->vrc);
1199
                                        }
1200
                                        session->vrc = NULL;
1201
                                } else {
1202
                                        /* We've started recording, send a PLI and go on */
1203
                                        char filename[255];
1204
                                        gint64 now = janus_get_real_time();
1205
                                        if(session->has_audio) {
1206
                                                memset(filename, 0, 255);
1207
                                                if(recording_base) {
1208
                                                        /* Use the filename and path we have been provided */
1209
                                                        g_snprintf(filename, 255, "%s-audio", recording_base);
1210
                                                        session->arc = janus_recorder_create(NULL, 0, filename);
1211
                                                        if(session->arc == NULL) {
1212
                                                                /* FIXME We should notify the fact the recorder could not be created */
1213
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this VideoCall user!\n");
1214
                                                        }
1215
                                                } else {
1216
                                                        /* Build a filename */
1217
                                                        g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-audio",
1218
                                                                session->username ? session->username : "unknown",
1219
                                                                (session->peer && session->peer->username) ? session->peer->username : "unknown",
1220
                                                                now);
1221
                                                        session->arc = janus_recorder_create(NULL, 0, filename);
1222
                                                        if(session->arc == NULL) {
1223
                                                                /* FIXME We should notify the fact the recorder could not be created */
1224
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this VideoCall user!\n");
1225
                                                        }
1226
                                                }
1227
                                        }
1228
                                        if(session->has_video) {
1229
                                                memset(filename, 0, 255);
1230
                                                if(recording_base) {
1231
                                                        /* Use the filename and path we have been provided */
1232
                                                        g_snprintf(filename, 255, "%s-video", recording_base);
1233
                                                        session->vrc = janus_recorder_create(NULL, 1, filename);
1234
                                                        if(session->vrc == NULL) {
1235
                                                                /* FIXME We should notify the fact the recorder could not be created */
1236
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this VideoCall user!\n");
1237
                                                        }
1238
                                                } else {
1239
                                                        /* Build a filename */
1240
                                                        g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-video",
1241
                                                                session->username ? session->username : "unknown",
1242
                                                                (session->peer && session->peer->username) ? session->peer->username : "unknown",
1243
                                                                now);
1244
                                                        session->vrc = janus_recorder_create(NULL, 1, filename);
1245
                                                        if(session->vrc == NULL) {
1246
                                                                /* FIXME We should notify the fact the recorder could not be created */
1247
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this VideoCall user!\n");
1248
                                                        }
1249
                                                }
1250
                                                /* Send a PLI */
1251
                                                JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n");
1252
                                                char buf[12];
1253
                                                memset(buf, 0, 12);
1254
                                                janus_rtcp_pli((char *)&buf, 12);
1255
                                                gateway->relay_rtcp(session->handle, 1, buf, 12);
1256
                                        }
1257
                                }
1258
                        }
1259
                        /* Send an ack back */
1260
                        result = json_object();
1261
                        json_object_set_new(result, "event", json_string("set"));
1262
                } else if(!strcasecmp(request_text, "hangup")) {
1263
                        /* Hangup an ongoing call or reject an incoming one */
1264
                        janus_mutex_lock(&sessions_mutex);
1265
                        janus_videocall_session *peer = session->peer;
1266
                        if(peer == NULL) {
1267
                                JANUS_LOG(LOG_WARN, "No call to hangup\n");
1268
                        } else {
1269
                                JANUS_LOG(LOG_VERB, "%s is hanging up the call with %s\n", session->username, peer->username);
1270
                                session->peer = NULL;
1271
                                peer->peer = NULL;
1272
                        }
1273
                        janus_mutex_unlock(&sessions_mutex);
1274
                        /* Notify the success as an hangup message */
1275
                        result = json_object();
1276
                        json_object_set_new(result, "event", json_string("hangup"));
1277
                        json_object_set_new(result, "username", json_string(session->username));
1278
                        json_object_set_new(result, "reason", json_string("We did the hangup"));
1279
                        if(peer != NULL) {
1280
                                /* Send event to our peer too */
1281
                                json_t *call = json_object();
1282
                                json_object_set_new(call, "videocall", json_string("event"));
1283
                                json_t *calling = json_object();
1284
                                json_object_set_new(calling, "event", json_string("hangup"));
1285
                                json_object_set_new(calling, "username", json_string(session->username));
1286
                                json_object_set_new(calling, "reason", json_string("Remote hangup"));
1287
                                json_object_set_new(call, "result", calling);
1288
                                char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1289
                                json_decref(call);
1290
                                JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text);
1291
                                int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL);
1292
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1293
                                g_free(call_text);
1294
                        }
1295
                } else {
1296
                        JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text);
1297
                        error_code = JANUS_VIDEOCALL_ERROR_INVALID_REQUEST;
1298
                        g_snprintf(error_cause, 512, "Unknown request (%s)", request_text);
1299
                        goto error;
1300
                }
1301

    
1302
                json_decref(root);
1303
                /* Prepare JSON event */
1304
                json_t *event = json_object();
1305
                json_object_set_new(event, "videocall", json_string("event"));
1306
                if(result != NULL)
1307
                        json_object_set_new(event, "result", result);
1308
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1309
                json_decref(event);
1310
                JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
1311
                int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, sdp_type, sdp);
1312
                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1313
                g_free(event_text);
1314
                if(sdp)
1315
                        g_free(sdp);
1316
                janus_videocall_message_free(msg);
1317
                continue;
1318
                
1319
error:
1320
                {
1321
                        if(root != NULL)
1322
                                json_decref(root);
1323
                        /* Prepare JSON error event */
1324
                        json_t *event = json_object();
1325
                        json_object_set_new(event, "videocall", json_string("event"));
1326
                        json_object_set_new(event, "error_code", json_integer(error_code));
1327
                        json_object_set_new(event, "error", json_string(error_cause));
1328
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1329
                        json_decref(event);
1330
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
1331
                        int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, NULL, NULL);
1332
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1333
                        g_free(event_text);
1334
                        janus_videocall_message_free(msg);
1335
                }
1336
        }
1337
        g_free(error_cause);
1338
        JANUS_LOG(LOG_VERB, "Leaving VideoCall handler thread\n");
1339
        return NULL;
1340
}