Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ 242e4f31

History | View | Annotate | Download (186 KB)

1
/*! \file   janus_videoroom.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief  Janus VideoRoom plugin
5
 * \details  This is a plugin implementing a videoconferencing SFU
6
 * (Selective Forwarding Unit) for Janus, that is an audio/video router.
7
 * This means that the plugin implements a virtual conferencing room peers
8
 * can join and leave at any time. This room is based on a Publish/Subscribe
9
 * pattern. Each peer can publish his/her own live audio/video feeds: this
10
 * feed becomes an available stream in the room the other participants can
11
 * attach to. This means that this plugin allows the realization of several
12
 * different scenarios, ranging from a simple webinar (one speaker, several
13
 * listeners) to a fully meshed video conference (each peer sending and
14
 * receiving to and from all the others).
15
 * 
16
 * For what concerns the subscriber side, there are two different ways to
17
 * attach to a publisher's feed: a generic 'listener', which can attach to
18
 * a single feed, and a more complex 'Multiplexed listener', which instead can
19
 * attach to more feeds using the same PeerConnection. The generic 'listener'
20
 * is the default, which means that if you want to watch more feeds at the
21
 * same time, you'll need to create multiple 'listeners' to attach at any
22
 * of them. The 'Multiplexed listener', instead, is a more complex alternative
23
 * that exploits the so called RTCWEB 'Plan B', which multiplexes more
24
 * streams on a single PeerConnection and in the SDP: while more efficient in terms of
25
 * resources, though, this approach is experimental, and currently only
26
 * available on Google Chrome, so use it wisely.
27
 * \note As of now, work on Plan B is still going on, and as such its support in Janus
28
 * is flaky to say the least. Don't try to attach as a Multiplexed listener or bad
29
 * things will probably happen!
30
 * 
31
 * Considering that this plugin allows for several different WebRTC PeerConnections
32
 * to be on at the same time for the same peer (specifically, each peer
33
 * potentially has 1 PeerConnection on for publishing and N on for subscriptions
34
 * from other peers), each peer may need to attach several times to the same
35
 * plugin for every stream: this means that each peer needs to have at least one
36
 * handle active for managing its relation with the plugin (joining a room,
37
 * leaving a room, muting/unmuting, publishing, receiving events), and needs
38
 * to open a new one each time he/she wants to subscribe to a feed from
39
 * another participant (or a single one in case a 'Multiplexed listener is used).
40
 * The handle used for a subscription, however, would be logically a "slave"
41
 * to the master one used for managing the room: this means that it cannot
42
 * be used, for instance, to unmute in the room, as its only purpose would
43
 * be to provide a context in which creating the sendonly PeerConnection
44
 * for the subscription to the active participant.
45
 * 
46
 * Rooms to make available are listed in the plugin configuration file.
47
 * A pre-filled configuration file is provided in \c conf/janus.plugin.videoroom.cfg
48
 * and includes a demo room for testing. The same plugin is also used
49
 * dynamically (that is, with rooms created on the fly via API) in the
50
 * Screen Sharing demo as well.
51
 * 
52
 * To add more rooms or modify the existing one, you can use the following
53
 * syntax:
54
 * 
55
 * \verbatim
56
[<unique room ID>]
57
description = This is my awesome room
58
is_private = yes|no (private rooms don't appear when you do a 'list' request)
59
secret = <optional password needed for manipulating (e.g. destroying) the room>
60
pin = <optional password needed for joining the room>
61
require_pvtid = yes|no (whether subscriptions are required to provide a valid
62
             a valid private_id to associate with a publisher, default=no)
63
publishers = <max number of concurrent senders> (e.g., 6 for a video
64
             conference or 1 for a webinar)
65
bitrate = <max video bitrate for senders> (e.g., 128000)
66
fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
67
audiocodec = opus|isac32|isac16|pcmu|pcma|g722 (audio codec to force on publishers, default=opus)
68
videocodec = vp8|vp9|h264 (video codec to force on publishers, default=vp8)
69
video_svc = yes|no (whether SVC support must be enabled; works only for VP9, default=no)
70
audiolevel_ext = yes|no (whether the ssrc-audio-level RTP extension must be
71
        negotiated/used or not for new publishers, default=yes)
72
audiolevel_event = yes|no (whether to emit event to other users or not)
73
audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds)
74
audio_level_average = 25 (average value of audio level, 127=muted, 0='too loud', default=25)
75
videoorientation_ext = yes|no (whether the video-orientation RTP extension must be
76
        negotiated/used or not for new publishers, default=yes)
77
playoutdelay_ext = yes|no (whether the playout-delay RTP extension must be
78
        negotiated/used or not for new publishers, default=yes)
79
record = true|false (whether this room should be recorded, default=false)
80
rec_dir = <folder where recordings should be stored, when enabled>
81
\endverbatim
82
 *
83
 * Note that recording will work with all codecs except iSAC.
84
 *
85
 * \section sfuapi Video Room API
86
 * 
87
 * The Video Room API supports several requests, some of which are
88
 * synchronous and some asynchronous. There are some situations, though,
89
 * (invalid JSON, invalid request) which will always result in a
90
 * synchronous error response even for asynchronous requests. 
91
 * 
92
 * \c create , \c destroy , \c exists, \c list, \c allowed, \c kick and
93
 * and \c listparticipants are synchronous requests, which means you'll
94
 * get a response directly within the context of the transaction.
95
 * \c create allows you to create a new video room dynamically, as an
96
 * alternative to using the configuration file; \c destroy removes a
97
 * video room and destroys it, kicking all the users out as part of the
98
 * process; \c exists allows you to check whether a specific video room
99
 * exists; finally, \c list lists all the available rooms, while \c
100
 * listparticipants lists all the participants of a specific room and
101
 * their details.
102
 * 
103
 * The \c join , \c joinandconfigure , \c configure , \c publish ,
104
 * \c unpublish , \c start , \c pause , \c switch , \c stop , \c add ,
105
 * \c remove and \c leave requests instead are all asynchronous, which
106
 * means you'll get a notification about their success or failure in
107
 * an event. \c join allows you to join a specific video room, specifying
108
 * whether that specific PeerConnection will be used for publishing or
109
 * watching; \c configure can be used to modify some of the participation
110
 * settings (e.g., bitrate cap); \c joinandconfigure combines the previous
111
 * two requests in a single one (just for publishers); \c publish can be
112
 * used to start sending media to broadcast to the other participants,
113
 * while \c unpublish does the opposite; \c start allows you to start
114
 * receiving media from a publisher you've subscribed to previously by
115
 * means of a \c join , while \c pause pauses the delivery of the media;
116
 * the \c switch request can be used to change the source of the media
117
 * flowing over a specific PeerConnection (e.g., I was watching Alice,
118
 * I want to watch Bob now) without having to create a new handle for
119
 * that; \c stop interrupts a viewer instance; finally, \c leave allows
120
 * you to leave a video room for good.
121
 * 
122
 * Notice that, in general, all users can create rooms. If you want to
123
 * limit this functionality, you can configure an admin \c admin_key in
124
 * the plugin settings. When configured, only "create" requests that
125
 * include the correct \c admin_key value in an "admin_key" property
126
 * will succeed, and will be rejected otherwise.
127
 * 
128
 * Actual API docs: TBD.
129
 * 
130
 * \ingroup plugins
131
 * \ref plugins
132
 */
133

    
134
#include "plugin.h"
135

    
136
#include <jansson.h>
137

    
138
#include "../debug.h"
139
#include "../apierror.h"
140
#include "../config.h"
141
#include "../mutex.h"
142
#include "../rtp.h"
143
#include "../rtcp.h"
144
#include "../record.h"
145
#include "../sdp-utils.h"
146
#include "../utils.h"
147
#include <sys/types.h>
148
#include <sys/socket.h>
149

    
150

    
151
/* Plugin information */
152
#define JANUS_VIDEOROOM_VERSION                        9
153
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.9"
154
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing SFU (Selective Forwarding Unit) for Janus, that is an audio/video router."
155
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
156
#define JANUS_VIDEOROOM_AUTHOR                        "Meetecho s.r.l."
157
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
158

    
159
/* Plugin methods */
160
janus_plugin *create(void);
161
int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
162
void janus_videoroom_destroy(void);
163
int janus_videoroom_get_api_compatibility(void);
164
int janus_videoroom_get_version(void);
165
const char *janus_videoroom_get_version_string(void);
166
const char *janus_videoroom_get_description(void);
167
const char *janus_videoroom_get_name(void);
168
const char *janus_videoroom_get_author(void);
169
const char *janus_videoroom_get_package(void);
170
void janus_videoroom_create_session(janus_plugin_session *handle, int *error);
171
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
172
void janus_videoroom_setup_media(janus_plugin_session *handle);
173
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
174
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
175
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len);
176
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video);
177
void janus_videoroom_hangup_media(janus_plugin_session *handle);
178
void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error);
179
json_t *janus_videoroom_query_session(janus_plugin_session *handle);
180

    
181
/* Plugin setup */
182
static janus_plugin janus_videoroom_plugin =
183
        JANUS_PLUGIN_INIT (
184
                .init = janus_videoroom_init,
185
                .destroy = janus_videoroom_destroy,
186

    
187
                .get_api_compatibility = janus_videoroom_get_api_compatibility,
188
                .get_version = janus_videoroom_get_version,
189
                .get_version_string = janus_videoroom_get_version_string,
190
                .get_description = janus_videoroom_get_description,
191
                .get_name = janus_videoroom_get_name,
192
                .get_author = janus_videoroom_get_author,
193
                .get_package = janus_videoroom_get_package,
194
                
195
                .create_session = janus_videoroom_create_session,
196
                .handle_message = janus_videoroom_handle_message,
197
                .setup_media = janus_videoroom_setup_media,
198
                .incoming_rtp = janus_videoroom_incoming_rtp,
199
                .incoming_rtcp = janus_videoroom_incoming_rtcp,
200
                .incoming_data = janus_videoroom_incoming_data,
201
                .slow_link = janus_videoroom_slow_link,
202
                .hangup_media = janus_videoroom_hangup_media,
203
                .destroy_session = janus_videoroom_destroy_session,
204
                .query_session = janus_videoroom_query_session,
205
        );
206

    
207
/* Plugin creator */
208
janus_plugin *create(void) {
209
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
210
        return &janus_videoroom_plugin;
211
}
212

    
213
/* Parameter validation */
214
static struct janus_json_parameter request_parameters[] = {
215
        {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
216
};
217
static struct janus_json_parameter adminkey_parameters[] = {
218
        {"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
219
};
220
static struct janus_json_parameter create_parameters[] = {
221
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
222
        {"description", JSON_STRING, 0},
223
        {"is_private", JANUS_JSON_BOOL, 0},
224
        {"allowed", JSON_ARRAY, 0},
225
        {"secret", JSON_STRING, 0},
226
        {"pin", JSON_STRING, 0},
227
        {"require_pvtid", JANUS_JSON_BOOL, 0},
228
        {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
229
        {"fir_freq", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
230
        {"publishers", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
231
        {"audiocodec", JSON_STRING, 0},
232
        {"videocodec", JSON_STRING, 0},
233
        {"video_svc", JANUS_JSON_BOOL, 0},
234
        {"audiolevel_ext", JANUS_JSON_BOOL, 0},
235
        {"audiolevel_event", JANUS_JSON_BOOL, 0},
236
        {"audio_active_packets", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
237
        {"audio_level_average", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
238
        {"videoorient_ext", JANUS_JSON_BOOL, 0},
239
        {"playoutdelay_ext", JANUS_JSON_BOOL, 0},
240
        {"record", JANUS_JSON_BOOL, 0},
241
        {"rec_dir", JSON_STRING, 0},
242
        {"permanent", JANUS_JSON_BOOL, 0}
243
};
244
static struct janus_json_parameter room_parameters[] = {
245
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
246
};
247
static struct janus_json_parameter destroy_parameters[] = {
248
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
249
        {"permanent", JANUS_JSON_BOOL, 0}
250
};
251
static struct janus_json_parameter allowed_parameters[] = {
252
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
253
        {"secret", JSON_STRING, 0},
254
        {"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
255
        {"allowed", JSON_ARRAY, 0}
256
};
257
static struct janus_json_parameter kick_parameters[] = {
258
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
259
        {"secret", JSON_STRING, 0},
260
        {"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
261
};
262
static struct janus_json_parameter join_parameters[] = {
263
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
264
        {"ptype", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
265
        {"audio", JANUS_JSON_BOOL, 0},
266
        {"video", JANUS_JSON_BOOL, 0},
267
        {"data", JANUS_JSON_BOOL, 0},
268
        {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
269
        {"record", JANUS_JSON_BOOL, 0},
270
        {"filename", JSON_STRING, 0}
271
};
272
static struct janus_json_parameter publish_parameters[] = {
273
        {"audio", JANUS_JSON_BOOL, 0},
274
        {"video", JANUS_JSON_BOOL, 0},
275
        {"data", JANUS_JSON_BOOL, 0},
276
        {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
277
        {"record", JANUS_JSON_BOOL, 0},
278
        {"filename", JSON_STRING, 0},
279
        {"display", JSON_STRING, 0}
280
};
281
static struct janus_json_parameter rtp_forward_parameters[] = {
282
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
283
        {"publisher_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
284
        {"video_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
285
        {"video_ssrc", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
286
        {"video_pt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
287
        {"audio_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
288
        {"audio_ssrc", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
289
        {"audio_pt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
290
        {"data_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
291
        {"host", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
292
};
293
static struct janus_json_parameter stop_rtp_forward_parameters[] = {
294
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
295
        {"publisher_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
296
        {"stream_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
297
};
298
static struct janus_json_parameter publisher_parameters[] = {
299
        {"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
300
        {"display", JSON_STRING, 0}
301
};
302
static struct janus_json_parameter configure_parameters[] = {
303
        {"audio", JANUS_JSON_BOOL, 0},
304
        {"video", JANUS_JSON_BOOL, 0},
305
        {"data", JANUS_JSON_BOOL, 0},
306
        {"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
307
        {"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
308
};
309
static struct janus_json_parameter listener_parameters[] = {
310
        {"feed", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
311
        {"private_id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
312
        {"audio", JANUS_JSON_BOOL, 0},
313
        {"video", JANUS_JSON_BOOL, 0},
314
        {"data", JANUS_JSON_BOOL, 0}
315
};
316

    
317
/* Static configuration instance */
318
static janus_config *config = NULL;
319
static const char *config_folder = NULL;
320
static janus_mutex config_mutex;
321

    
322
/* Useful stuff */
323
static volatile gint initialized = 0, stopping = 0;
324
static gboolean notify_events = TRUE;
325
static janus_callbacks *gateway = NULL;
326
static GThread *handler_thread;
327
static GThread *watchdog;
328
static void *janus_videoroom_handler(void *data);
329
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
330
static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data);
331

    
332
typedef enum janus_videoroom_p_type {
333
        janus_videoroom_p_type_none = 0,
334
        janus_videoroom_p_type_subscriber,                        /* Generic listener/subscriber */
335
        janus_videoroom_p_type_publisher,                        /* Participant/publisher */
336
} janus_videoroom_p_type;
337

    
338
typedef struct janus_videoroom_message {
339
        janus_plugin_session *handle;
340
        char *transaction;
341
        json_t *message;
342
        json_t *jsep;
343
} janus_videoroom_message;
344
static GAsyncQueue *messages = NULL;
345
static janus_videoroom_message exit_message;
346

    
347
static void janus_videoroom_message_free(janus_videoroom_message *msg) {
348
        if(!msg || msg == &exit_message)
349
                return;
350

    
351
        msg->handle = NULL;
352

    
353
        g_free(msg->transaction);
354
        msg->transaction = NULL;
355
        if(msg->message)
356
                json_decref(msg->message);
357
        msg->message = NULL;
358
        if(msg->jsep)
359
                json_decref(msg->jsep);
360
        msg->jsep = NULL;
361

    
362
        g_free(msg);
363
}
364

    
365
/* Payload types we'll offer internally */
366
#define OPUS_PT                111
367
#define ISAC32_PT        104
368
#define ISAC16_PT        103
369
#define PCMU_PT                0
370
#define PCMA_PT                8
371
#define G722_PT                9
372
#define VP8_PT                96
373
#define VP9_PT                101
374
#define H264_PT                107
375

    
376
typedef enum janus_videoroom_audiocodec {
377
        JANUS_VIDEOROOM_OPUS,                /* Publishers will have to use OPUS         */
378
        JANUS_VIDEOROOM_ISAC_32K,        /* Publishers will have to use ISAC 32K */
379
        JANUS_VIDEOROOM_ISAC_16K,        /* Publishers will have to use ISAC 16K */
380
        JANUS_VIDEOROOM_PCMU,                /* Publishers will have to use PCMU 8K         */
381
        JANUS_VIDEOROOM_PCMA,                /* Publishers will have to use PCMA 8K         */
382
        JANUS_VIDEOROOM_G722                /* Publishers will have to use G.722         */
383
} janus_videoroom_audiocodec;
384
static const char *janus_videoroom_audiocodec_name(janus_videoroom_audiocodec acodec) {
385
        switch(acodec) {
386
                case JANUS_VIDEOROOM_OPUS:
387
                        return "opus";
388
                case JANUS_VIDEOROOM_ISAC_32K:
389
                        return "isac32";
390
                case JANUS_VIDEOROOM_ISAC_16K:
391
                        return "isac16";
392
                case JANUS_VIDEOROOM_PCMU:
393
                        return "pcmu";
394
                case JANUS_VIDEOROOM_PCMA:
395
                        return "pcma";
396
                case JANUS_VIDEOROOM_G722:
397
                        return "g722";
398
                default:
399
                        /* Shouldn't happen */
400
                        return "opus";
401
        }
402
}
403
static int janus_videoroom_audiocodec_pt(janus_videoroom_audiocodec acodec) {
404
        switch(acodec) {
405
                case JANUS_VIDEOROOM_OPUS:
406
                        return OPUS_PT;
407
                case JANUS_VIDEOROOM_ISAC_32K:
408
                        return ISAC32_PT;
409
                case JANUS_VIDEOROOM_ISAC_16K:
410
                        return ISAC16_PT;
411
                case JANUS_VIDEOROOM_PCMU:
412
                        return PCMU_PT;
413
                case JANUS_VIDEOROOM_PCMA:
414
                        return PCMA_PT;
415
                case JANUS_VIDEOROOM_G722:
416
                        return G722_PT;
417
                default:
418
                        /* Shouldn't happen */
419
                        return OPUS_PT;
420
        }
421
}
422

    
423
typedef enum janus_videoroom_videocodec {
424
        JANUS_VIDEOROOM_VP8,        /* Publishers will have to use VP8 */
425
        JANUS_VIDEOROOM_VP9,        /* Publishers will have to use VP9 */
426
        JANUS_VIDEOROOM_H264        /* Publishers will have to use H264 */
427
} janus_videoroom_videocodec;
428
static const char *janus_videoroom_videocodec_name(janus_videoroom_videocodec vcodec) {
429
        switch(vcodec) {
430
                case JANUS_VIDEOROOM_VP8:
431
                        return "vp8";
432
                case JANUS_VIDEOROOM_VP9:
433
                        return "vp9";
434
                case JANUS_VIDEOROOM_H264:
435
                        return "h264";
436
                default:
437
                        /* Shouldn't happen */
438
                        return "vp8";
439
        }
440
}
441
static int janus_videoroom_videocodec_pt(janus_videoroom_videocodec vcodec) {
442
        switch(vcodec) {
443
                case JANUS_VIDEOROOM_VP8:
444
                        return VP8_PT;
445
                case JANUS_VIDEOROOM_VP9:
446
                        return VP9_PT;
447
                case JANUS_VIDEOROOM_H264:
448
                        return H264_PT;
449
                default:
450
                        /* Shouldn't happen */
451
                        return VP8_PT;
452
        }
453
}
454
/* Helper method to parse an RTP video frame and get some SVC-related info
455
 * (note: this only works with VP9, right now, on an experimental basis) */
456
static int janus_videoroom_videocodec_parse_svc(janus_videoroom_videocodec vcodec,
457
        char *buf, int total, int *found,
458
        int *spatial_layer, int *temporal_layer,
459
        uint8_t *p, uint8_t *d, uint8_t *u, uint8_t *b, uint8_t *e);
460

    
461

    
462
typedef struct janus_videoroom {
463
        guint64 room_id;                        /* Unique room ID */
464
        gchar *room_name;                        /* Room description */
465
        gchar *room_secret;                        /* Secret needed to manipulate (e.g., destroy) this room */
466
        gchar *room_pin;                        /* Password needed to join this room, if any */
467
        gboolean is_private;                /* Whether this room is 'private' (as in hidden) or not */
468
        gboolean require_pvtid;                /* Whether subscriptions in this room require a private_id */
469
        int max_publishers;                        /* Maximum number of concurrent publishers */
470
        uint64_t bitrate;                        /* Global bitrate limit */
471
        uint16_t fir_freq;                        /* Regular FIR frequency (0=disabled) */
472
        janus_videoroom_audiocodec acodec;        /* Audio codec to force on publishers*/
473
        janus_videoroom_videocodec vcodec;        /* Video codec to force on publishers*/
474
        gboolean do_svc;                        /* Whether SVC must be done for video (note: only available for VP9 right now) */
475
        gboolean audiolevel_ext;        /* Whether the ssrc-audio-level extension must be negotiated or not for new publishers */
476
        gboolean audiolevel_event;        /* Whether to emit event to other users about audiolevel */
477
        int audio_active_packets;        /* amount of packets with audio level for checkup */
478
        int audio_level_average;        /* average audio level */
479
        gboolean videoorient_ext;        /* Whether the video-orientation extension must be negotiated or not for new publishers */
480
        gboolean playoutdelay_ext;        /* Whether the playout-delay extension must be negotiated or not for new publishers */
481
        gboolean record;                        /* Whether the feeds from publishers in this room should be recorded */
482
        char *rec_dir;                                /* Where to save the recordings of this room, if enabled */
483
        gint64 destroyed;                        /* Value to flag the room for destruction, done lazily */
484
        GHashTable *participants;        /* Map of potential publishers (we get listeners from them) */
485
        GHashTable *private_ids;        /* Map of existing private IDs */
486
        gboolean check_tokens;                /* Whether to check tokens when participants join (see below) */
487
        GHashTable *allowed;                /* Map of participants (as tokens) allowed to join */
488
        janus_mutex participants_mutex;/* Mutex to protect room properties */
489
} janus_videoroom;
490
static GHashTable *rooms;
491
static janus_mutex rooms_mutex;
492
static GList *old_rooms;
493
static char *admin_key = NULL;
494
static void janus_videoroom_free(janus_videoroom *room);
495

    
496
typedef struct janus_videoroom_session {
497
        janus_plugin_session *handle;
498
        janus_videoroom_p_type participant_type;
499
        gpointer participant;
500
        gboolean started;
501
        gboolean stopping;
502
        volatile gint hangingup;
503
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
504
} janus_videoroom_session;
505
static GHashTable *sessions;
506
static GList *old_sessions;
507
static janus_mutex sessions_mutex;
508

    
509
/* a host whose ports gets streamed rtp packets of the corresponding type. */
510
typedef struct janus_videoroom_rtp_forwarder {
511
        gboolean is_video;
512
        gboolean is_data;
513
        uint32_t ssrc;
514
        int payload_type;
515
        struct sockaddr_in serv_addr;
516
} janus_videoroom_rtp_forwarder;
517

    
518
typedef struct janus_videoroom_participant {
519
        janus_videoroom_session *session;
520
        janus_videoroom *room;        /* Room */
521
        guint64 user_id;        /* Unique ID in the room */
522
        guint32 pvt_id;                /* This is sent to the publisher for mapping purposes, but shouldn't be shared with others */
523
        gchar *display;                /* Display name (just for fun) */
524
        gchar *sdp;                        /* The SDP this publisher negotiated, if any */
525
        gboolean audio, video, data;                /* Whether audio, video and/or data is going to be sent by this publisher */
526
        guint32 audio_pt;                /* Audio payload type (Opus) */
527
        guint32 video_pt;                /* Video payload type (depends on room configuration) */
528
        guint32 audio_ssrc;                /* Audio SSRC of this publisher */
529
        guint32 video_ssrc;                /* Video SSRC of this publisher */
530
        guint8 audio_level_extmap_id;        /* Audio level extmap ID */
531
        guint8 video_orient_extmap_id;        /* Video orientation extmap ID */
532
        guint8 playout_delay_extmap_id;        /* Playout delay extmap ID */
533
        gboolean audio_active;
534
        gboolean video_active;
535
        int audio_dBov_level;                /* Value in dBov of the audio level (last value from extension) */
536
        int audio_active_packets;        /* Participant's number of audio packets to accumulate */
537
        int audio_dBov_sum;                        /* Participant's accumulated dBov value for audio level*/
538
        gboolean talking;                        /* Whether this participant is currently talking (uses audio levels extension) */
539
        gboolean data_active;
540
        gboolean firefox;        /* We send Firefox users a different kind of FIR */
541
        uint64_t bitrate;
542
        gint64 remb_startup;/* Incremental changes on REMB to reach the target at startup */
543
        gint64 remb_latest;        /* Time of latest sent REMB (to avoid flooding) */
544
        gint64 fir_latest;        /* Time of latest sent FIR (to avoid flooding) */
545
        gint fir_seq;                /* FIR sequence number */
546
        gboolean recording_active;        /* Whether this publisher has to be recorded or not */
547
        gchar *recording_base;        /* Base name for the recording (e.g., /path/to/filename, will generate /path/to/filename-audio.mjr and/or /path/to/filename-video.mjr */
548
        janus_recorder *arc;        /* The Janus recorder instance for this publisher's audio, if enabled */
549
        janus_recorder *vrc;        /* The Janus recorder instance for this publisher's video, if enabled */
550
        janus_recorder *drc;        /* The Janus recorder instance for this publisher's data, if enabled */
551
        janus_mutex rec_mutex;        /* Mutex to protect the recorders from race conditions */
552
        GSList *listeners;                /* Subscriptions to this publisher (who's watching this publisher)  */
553
        GSList *subscriptions;        /* Subscriptions this publisher has created (who this publisher is watching) */
554
        janus_mutex listeners_mutex;
555
        GHashTable *rtp_forwarders;
556
        janus_mutex rtp_forwarders_mutex;
557
        int udp_sock; /* The udp socket on which to forward rtp packets */
558
        gboolean kicked;        /* Whether this participant has been kicked */
559
} janus_videoroom_participant;
560
static void janus_videoroom_participant_free(janus_videoroom_participant *p);
561
static void janus_videoroom_rtp_forwarder_free_helper(gpointer data);
562
static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_participant *p,
563
        const gchar* host, int port, int pt, uint32_t ssrc, gboolean is_video, gboolean is_data);
564

    
565
typedef struct janus_videoroom_listener {
566
        janus_videoroom_session *session;
567
        janus_videoroom *room;        /* Room */
568
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
569
        guint32 pvt_id;                /* Private ID of the participant that is subscribing (if available/provided) */
570
        janus_rtp_switching_context context;        /* Needed in case there are publisher switches on this listener */
571
        gboolean audio, video, data;                /* Whether audio, video and/or data must be sent to this publisher */
572
        gboolean paused;
573
        gboolean kicked;        /* Whether this subscription belongs to a participant that has been kicked */
574
        /* The following are only relevant if we're doing VP9 SVC*/
575
        int spatial_layer, target_spatial_layer;
576
        int temporal_layer, target_temporal_layer;
577
} janus_videoroom_listener;
578
static void janus_videoroom_listener_free(janus_videoroom_listener *l);
579

    
580
typedef struct janus_videoroom_rtp_relay_packet {
581
        rtp_header *data;
582
        gint length;
583
        gboolean is_video;
584
        uint32_t timestamp;
585
        uint16_t seq_number;
586
        /* The following are only relevant if we're doing VP9 SVC*/
587
        gboolean svc;
588
        int spatial_layer;
589
        int temporal_layer;
590
        uint8_t pbit, dbit, ubit, bbit, ebit;
591
} janus_videoroom_rtp_relay_packet;
592

    
593

    
594
/* Error codes */
595
#define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR                499
596
#define JANUS_VIDEOROOM_ERROR_NO_MESSAGE                421
597
#define JANUS_VIDEOROOM_ERROR_INVALID_JSON                422
598
#define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST        423
599
#define JANUS_VIDEOROOM_ERROR_JOIN_FIRST                424
600
#define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED        425
601
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM                426
602
#define JANUS_VIDEOROOM_ERROR_ROOM_EXISTS                427
603
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED                428
604
#define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT        429
605
#define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT        430
606
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE        431
607
#define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL        432
608
#define JANUS_VIDEOROOM_ERROR_UNAUTHORIZED                433
609
#define JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED        434
610
#define JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED                435
611
#define JANUS_VIDEOROOM_ERROR_ID_EXISTS                        436
612
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP                437
613

    
614

    
615
static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_participant *p,
616
                const gchar* host, int port, int pt, uint32_t ssrc, gboolean is_video, gboolean is_data) {
617
        if(!p || !host) {
618
                return 0;
619
        }
620
        janus_videoroom_rtp_forwarder *forward = g_malloc0(sizeof(janus_videoroom_rtp_forwarder));
621
        forward->is_video = is_video;
622
        forward->payload_type = pt;
623
        forward->ssrc = ssrc;
624
        forward->is_data = is_data;
625
        forward->serv_addr.sin_family = AF_INET;
626
        inet_pton(AF_INET, host, &(forward->serv_addr.sin_addr));
627
        forward->serv_addr.sin_port = htons(port);
628
        janus_mutex_lock(&p->rtp_forwarders_mutex);
629
        guint32 stream_id = janus_random_uint32();
630
        while(g_hash_table_lookup(p->rtp_forwarders, GUINT_TO_POINTER(stream_id)) != NULL) {
631
                stream_id = janus_random_uint32();
632
        }
633
        g_hash_table_insert(p->rtp_forwarders, GUINT_TO_POINTER(stream_id), forward);
634
        janus_mutex_unlock(&p->rtp_forwarders_mutex);
635
        JANUS_LOG(LOG_VERB, "Added %s rtp_forward to participant %"SCNu64" host: %s:%d stream_id: %"SCNu32"\n",
636
                is_data ? "data" : (is_video ? "video" : "audio"), p->user_id, host, port, stream_id);
637
        return stream_id;
638
}
639

    
640

    
641
/* Convenience function for freeing a session */
642
static void session_free(gpointer data) {
643
        if(data) {
644
                janus_videoroom_session* session = (janus_videoroom_session*)data;
645
                switch(session->participant_type) {
646
                case janus_videoroom_p_type_publisher: 
647
                        janus_videoroom_participant_free(session->participant);
648
                        break;   
649
                case janus_videoroom_p_type_subscriber:
650
                        janus_videoroom_listener_free(session->participant);
651
                        break;
652
                default:
653
                        break;
654
                }
655
                session->handle = NULL;
656
                g_free(session);
657
                session = NULL;
658
        }
659
}
660

    
661
static void janus_videoroom_rtp_forwarder_free_helper(gpointer data) {
662
        if(data) {
663
                janus_videoroom_rtp_forwarder* forward = (janus_videoroom_rtp_forwarder*)data;
664
                if(forward) {
665
                        g_free(forward);
666
                        forward = NULL;
667
                }
668
        }
669
}
670

    
671
/* Convenience wrapper function for session_free that corresponds to GHRFunc() format for hash table cleanup */
672
static gboolean session_hash_table_remove(gpointer key, gpointer value, gpointer not_used) {
673
        if(value) {
674
                session_free(value);
675
        }
676
        return TRUE;
677
}
678

    
679
/* VideoRoom watchdog/garbage collector (sort of) */
680
static void *janus_videoroom_watchdog(void *data) {
681
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog started\n");
682
        gint64 now = 0, room_now = 0;
683
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
684
                janus_mutex_lock(&sessions_mutex);
685
                /* Iterate on all the participants/listeners and check if we need to remove any of them */
686
                now = janus_get_monotonic_time();
687
                if(old_sessions != NULL) {
688
                        GList *sl = old_sessions;
689
                        JANUS_LOG(LOG_HUGE, "Checking %d old VideoRoom sessions...\n", g_list_length(old_sessions));
690
                        while(sl) {
691
                                janus_videoroom_session *session = (janus_videoroom_session *)sl->data;
692
                                /* If we are stopping, their is no point to continue to iterate */
693
                                if(!initialized || stopping) {
694
                                        break;
695
                                }
696
                                if(!session) {
697
                                        sl = sl->next;
698
                                        continue;
699
                                }
700
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
701
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
702
                                        JANUS_LOG(LOG_VERB, "Freeing old VideoRoom session\n");
703
                                        GList *rm = sl->next;
704
                                        old_sessions = g_list_delete_link(old_sessions, sl);
705
                                        sl = rm;
706
                                        g_hash_table_steal(sessions, session->handle);
707
                                        session_free(session);
708
                                        continue;
709
                                }
710
                                sl = sl->next;
711
                        }
712
                }
713
                janus_mutex_unlock(&sessions_mutex);
714
                janus_mutex_lock(&rooms_mutex);
715
                if(old_rooms != NULL) {
716
                        GList *rl = old_rooms;
717
                        room_now = janus_get_monotonic_time();
718
                        while(rl) {
719
                                janus_videoroom* room = (janus_videoroom*)rl->data;
720
                                if(!initialized || stopping){
721
                                        break;
722
                                }
723
                                if(!room) {
724
                                        rl = rl->next;
725
                                        continue;
726
                                }
727
                                if(room_now - room->destroyed >= 5*G_USEC_PER_SEC) {
728
                                        GList *rm = rl->next;
729
                                        old_rooms = g_list_delete_link(old_rooms, rl);
730
                                        rl = rm;
731
                                        g_hash_table_remove(rooms, &room->room_id);
732
                                        continue;
733
                                }
734
                                rl = rl->next;
735
                        }
736
                }
737
                janus_mutex_unlock(&rooms_mutex);
738
                g_usleep(500000);
739
        }
740
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog stopped\n");
741
        return NULL;
742
}
743

    
744

    
745
/* Plugin implementation */
746
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
747
        if(g_atomic_int_get(&stopping)) {
748
                /* Still stopping from before */
749
                return -1;
750
        }
751
        if(callback == NULL || config_path == NULL) {
752
                /* Invalid arguments */
753
                return -1;
754
        }
755

    
756
        /* Read configuration */
757
        char filename[255];
758
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
759
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
760
        config = janus_config_parse(filename);
761
        config_folder = config_path;
762
        if(config != NULL)
763
                janus_config_print(config);
764
        janus_mutex_init(&config_mutex);
765

    
766
        rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal,
767
                (GDestroyNotify)g_free, (GDestroyNotify) janus_videoroom_free);
768
        janus_mutex_init(&rooms_mutex);
769
        sessions = g_hash_table_new(NULL, NULL);
770
        janus_mutex_init(&sessions_mutex);
771

    
772
        messages = g_async_queue_new_full((GDestroyNotify) janus_videoroom_message_free);
773

    
774
        /* This is the callback we'll need to invoke to contact the gateway */
775
        gateway = callback;
776

    
777
        /* Parse configuration to populate the rooms list */
778
        if(config != NULL) {
779
                /* Any admin key to limit who can "create"? */
780
                janus_config_item *key = janus_config_get_item_drilldown(config, "general", "admin_key");
781
                if(key != NULL && key->value != NULL)
782
                        admin_key = g_strdup(key->value);
783
                janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events");
784
                if(events != NULL && events->value != NULL)
785
                        notify_events = janus_is_true(events->value);
786
                if(!notify_events && callback->events_is_enabled()) {
787
                        JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_VIDEOROOM_NAME);
788
                }
789
                /* Iterate on all rooms */
790
                GList *cl = janus_config_get_categories(config);
791
                while(cl != NULL) {
792
                        janus_config_category *cat = (janus_config_category *)cl->data;
793
                        if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
794
                                cl = cl->next;
795
                                continue;
796
                        }
797
                        JANUS_LOG(LOG_VERB, "Adding video room '%s'\n", cat->name);
798
                        janus_config_item *desc = janus_config_get_item(cat, "description");
799
                        janus_config_item *priv = janus_config_get_item(cat, "is_private");
800
                        janus_config_item *secret = janus_config_get_item(cat, "secret");
801
                        janus_config_item *pin = janus_config_get_item(cat, "pin");
802
                        janus_config_item *req_pvtid = janus_config_get_item(cat, "require_pvtid");
803
                        janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
804
                        janus_config_item *maxp = janus_config_get_item(cat, "publishers");
805
                        janus_config_item *firfreq = janus_config_get_item(cat, "fir_freq");
806
                        janus_config_item *audiocodec = janus_config_get_item(cat, "audiocodec");
807
                        janus_config_item *videocodec = janus_config_get_item(cat, "videocodec");
808
                        janus_config_item *svc = janus_config_get_item(cat, "video_svc");
809
                        janus_config_item *audiolevel_ext = janus_config_get_item(cat, "audiolevel_ext");
810
                        janus_config_item *audiolevel_event = janus_config_get_item(cat, "audiolevel_event");
811
                        janus_config_item *audio_active_packets = janus_config_get_item(cat, "audio_active_packets");
812
                        janus_config_item *audio_level_average = janus_config_get_item(cat, "audio_level_average");
813
                        janus_config_item *videoorient_ext = janus_config_get_item(cat, "videoorient_ext");
814
                        janus_config_item *playoutdelay_ext = janus_config_get_item(cat, "playoutdelay_ext");
815
                        janus_config_item *record = janus_config_get_item(cat, "record");
816
                        janus_config_item *rec_dir = janus_config_get_item(cat, "rec_dir");
817
                        /* Create the video room */
818
                        janus_videoroom *videoroom = g_malloc0(sizeof(janus_videoroom));
819
                        videoroom->room_id = g_ascii_strtoull(cat->name, NULL, 0);
820
                        char *description = NULL;
821
                        if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
822
                                description = g_strdup(desc->value);
823
                        else
824
                                description = g_strdup(cat->name);
825
                        videoroom->room_name = description;
826
                        if(secret != NULL && secret->value != NULL) {
827
                                videoroom->room_secret = g_strdup(secret->value);
828
                        }
829
                        if(pin != NULL && pin->value != NULL) {
830
                                videoroom->room_pin = g_strdup(pin->value);
831
                        }
832
                        videoroom->is_private = priv && priv->value && janus_is_true(priv->value);
833
                        videoroom->require_pvtid = req_pvtid && req_pvtid->value && janus_is_true(req_pvtid->value);
834
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
835
                        if(maxp != NULL && maxp->value != NULL)
836
                                videoroom->max_publishers = atol(maxp->value);
837
                        if(videoroom->max_publishers < 0)
838
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
839
                        videoroom->bitrate = 0;
840
                        if(bitrate != NULL && bitrate->value != NULL)
841
                                videoroom->bitrate = atol(bitrate->value);
842
                        if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
843
                                videoroom->bitrate = 64000;        /* Don't go below 64k */
844
                        videoroom->fir_freq = 0;
845
                        if(firfreq != NULL && firfreq->value != NULL)
846
                                videoroom->fir_freq = atol(firfreq->value);
847
                        videoroom->acodec = JANUS_VIDEOROOM_OPUS;
848
                        if(audiocodec && audiocodec->value) {
849
                                if(!strcasecmp(audiocodec->value, "opus"))
850
                                        videoroom->acodec = JANUS_VIDEOROOM_OPUS;
851
                                else if(!strcasecmp(audiocodec->value, "isac32"))
852
                                        videoroom->acodec = JANUS_VIDEOROOM_ISAC_32K;
853
                                else if(!strcasecmp(audiocodec->value, "isac16"))
854
                                        videoroom->acodec = JANUS_VIDEOROOM_ISAC_16K;
855
                                else if(!strcasecmp(audiocodec->value, "pcmu"))
856
                                        videoroom->acodec = JANUS_VIDEOROOM_PCMU;
857
                                else if(!strcasecmp(audiocodec->value, "pcma"))
858
                                        videoroom->acodec = JANUS_VIDEOROOM_PCMA;
859
                                else if(!strcasecmp(audiocodec->value, "g722"))
860
                                        videoroom->acodec = JANUS_VIDEOROOM_G722;
861
                                else {
862
                                        JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s', falling back to OPUS\n", audiocodec->value);
863
                                        videoroom->acodec = JANUS_VIDEOROOM_OPUS;
864
                                }
865
                        }
866
                        videoroom->vcodec = JANUS_VIDEOROOM_VP8;
867
                        if(videocodec && videocodec->value) {
868
                                if(!strcasecmp(videocodec->value, "vp8"))
869
                                        videoroom->vcodec = JANUS_VIDEOROOM_VP8;
870
                                else if(!strcasecmp(videocodec->value, "vp9"))
871
                                        videoroom->vcodec = JANUS_VIDEOROOM_VP9;
872
                                else if(!strcasecmp(videocodec->value, "h264"))
873
                                        videoroom->vcodec = JANUS_VIDEOROOM_H264;
874
                                else {
875
                                        JANUS_LOG(LOG_WARN, "Unsupported video codec '%s', falling back to VP8\n", videocodec->value);
876
                                        videoroom->vcodec = JANUS_VIDEOROOM_VP8;
877
                                }
878
                        }
879
                        if(svc && svc->value && janus_is_true(svc->value)) {
880
                                if(videoroom->vcodec == JANUS_VIDEOROOM_VP9) {
881
                                        videoroom->do_svc = TRUE;
882
                                } else {
883
                                        JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9, not %s: disabling it...\n",
884
                                                janus_videoroom_videocodec_name(videoroom->vcodec));
885
                                }
886
                        }
887
                        videoroom->audiolevel_ext = TRUE;
888
                        if(audiolevel_ext != NULL && audiolevel_ext->value != NULL)
889
                                videoroom->audiolevel_ext = janus_is_true(audiolevel_ext->value);
890
                        videoroom->audiolevel_event = FALSE;
891
                        if(audiolevel_event != NULL && audiolevel_event->value != NULL)
892
                                videoroom->audiolevel_event = janus_is_true(audiolevel_event->value);
893
                        if(videoroom->audiolevel_event) {
894
                                videoroom->audio_active_packets = 100;
895
                                if(audio_active_packets != NULL && audio_active_packets->value != NULL){
896
                                        if(atoi(audio_active_packets->value) > 0) {
897
                                                videoroom->audio_active_packets = atoi(audio_active_packets->value);
898
                                        } else {
899
                                                JANUS_LOG(LOG_WARN, "Invalid audio_active_packets value, using default: %d\n", videoroom->audio_active_packets);
900
                                        }
901
                                }
902
                                videoroom->audio_level_average = 25;
903
                                if(audio_level_average != NULL && audio_level_average->value != NULL) {
904
                                        if(atoi(audio_level_average->value) > 0) {
905
                                                videoroom->audio_level_average = atoi(audio_level_average->value);
906
                                        } else {
907
                                                JANUS_LOG(LOG_WARN, "Invalid audio_level_average value provided, using default: %d\n", videoroom->audio_level_average);
908
                                        }
909
                                }
910
                        }
911
                        videoroom->videoorient_ext = TRUE;
912
                        if(videoorient_ext != NULL && videoorient_ext->value != NULL)
913
                                videoroom->videoorient_ext = janus_is_true(videoorient_ext->value);
914
                        videoroom->playoutdelay_ext = TRUE;
915
                        if(playoutdelay_ext != NULL && playoutdelay_ext->value != NULL)
916
                                videoroom->playoutdelay_ext = janus_is_true(playoutdelay_ext->value);
917
                        if(record && record->value) {
918
                                videoroom->record = janus_is_true(record->value);
919
                        }
920
                        if(rec_dir && rec_dir->value) {
921
                                videoroom->rec_dir = g_strdup(rec_dir->value);
922
                        }
923
                        videoroom->destroyed = 0;
924
                        janus_mutex_init(&videoroom->participants_mutex);
925
                        videoroom->participants = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
926
                        videoroom->private_ids = g_hash_table_new(NULL, NULL);
927
                        videoroom->check_tokens = FALSE;        /* Static rooms can't have an "allowed" list yet, no hooks to the configuration file */
928
                        videoroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
929
                        janus_mutex_lock(&rooms_mutex);
930
                        g_hash_table_insert(rooms, janus_uint64_dup(videoroom->room_id), videoroom);
931
                        janus_mutex_unlock(&rooms_mutex);
932
                        JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, %s/%s codecs, secret: %s, pin: %s, pvtid: %s)\n",
933
                                videoroom->room_id, videoroom->room_name,
934
                                videoroom->is_private ? "private" : "public",
935
                                janus_videoroom_audiocodec_name(videoroom->acodec),
936
                                janus_videoroom_videocodec_name(videoroom->vcodec),
937
                                videoroom->room_secret ? videoroom->room_secret : "no secret",
938
                                videoroom->room_pin ? videoroom->room_pin : "no pin",
939
                                videoroom->require_pvtid ? "required" : "optional");
940
                        if(videoroom->record) {
941
                                JANUS_LOG(LOG_VERB, "  -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
942
                        }
943
                        cl = cl->next;
944
                }
945
                /* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
946
        }
947

    
948
        /* Show available rooms */
949
        janus_mutex_lock(&rooms_mutex);
950
        GHashTableIter iter;
951
        gpointer value;
952
        g_hash_table_iter_init(&iter, rooms);
953
        while (g_hash_table_iter_next(&iter, NULL, &value)) {
954
                janus_videoroom *vr = value;
955
                JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds, %s audio codec, %s video codec\n",
956
                        vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq,
957
                        janus_videoroom_audiocodec_name(vr->acodec), janus_videoroom_videocodec_name(vr->vcodec));
958
        }
959
        janus_mutex_unlock(&rooms_mutex);
960

    
961
        g_atomic_int_set(&initialized, 1);
962

    
963
        GError *error = NULL;
964
        /* Start the sessions watchdog */
965
        watchdog = g_thread_try_new("videoroom watchdog", &janus_videoroom_watchdog, NULL, &error);
966
        if(error != NULL) {
967
                g_atomic_int_set(&initialized, 0);
968
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom watchdog thread...\n", error->code, error->message ? error->message : "??");
969
                janus_config_destroy(config);
970
                return -1;
971
        }
972
        /* Launch the thread that will handle incoming messages */
973
        handler_thread = g_thread_try_new("videoroom handler", janus_videoroom_handler, NULL, &error);
974
        if(error != NULL) {
975
                g_atomic_int_set(&initialized, 0);
976
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom handler thread...\n", error->code, error->message ? error->message : "??");
977
                janus_config_destroy(config);
978
                return -1;
979
        }
980
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
981
        return 0;
982
}
983

    
984
void janus_videoroom_destroy(void) {
985
        if(!g_atomic_int_get(&initialized))
986
                return;
987
        g_atomic_int_set(&stopping, 1);
988

    
989
        g_async_queue_push(messages, &exit_message);
990
        if(handler_thread != NULL) {
991
                g_thread_join(handler_thread);
992
                handler_thread = NULL;
993
        }
994
        if(watchdog != NULL) {
995
                g_thread_join(watchdog);
996
                watchdog = NULL;
997
        }
998

    
999
        /* FIXME We should destroy the sessions cleanly */
1000
        janus_mutex_lock(&sessions_mutex);
1001
        g_hash_table_foreach_remove(sessions, (GHRFunc)session_hash_table_remove, NULL);
1002
        g_hash_table_destroy(sessions);
1003
        sessions = NULL;
1004
        janus_mutex_unlock(&sessions_mutex);
1005

    
1006
        janus_mutex_lock(&rooms_mutex);
1007
        g_hash_table_destroy(rooms);
1008
        rooms = NULL;
1009
        janus_mutex_unlock(&rooms_mutex);
1010
        janus_mutex_destroy(&rooms_mutex);
1011

    
1012
        g_async_queue_unref(messages);
1013
        messages = NULL;
1014

    
1015
        janus_config_destroy(config);
1016
        g_free(admin_key);
1017

    
1018
        g_atomic_int_set(&initialized, 0);
1019
        g_atomic_int_set(&stopping, 0);
1020
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
1021
}
1022

    
1023
int janus_videoroom_get_api_compatibility(void) {
1024
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
1025
        return JANUS_PLUGIN_API_VERSION;
1026
}
1027

    
1028
int janus_videoroom_get_version(void) {
1029
        return JANUS_VIDEOROOM_VERSION;
1030
}
1031

    
1032
const char *janus_videoroom_get_version_string(void) {
1033
        return JANUS_VIDEOROOM_VERSION_STRING;
1034
}
1035

    
1036
const char *janus_videoroom_get_description(void) {
1037
        return JANUS_VIDEOROOM_DESCRIPTION;
1038
}
1039

    
1040
const char *janus_videoroom_get_name(void) {
1041
        return JANUS_VIDEOROOM_NAME;
1042
}
1043

    
1044
const char *janus_videoroom_get_author(void) {
1045
        return JANUS_VIDEOROOM_AUTHOR;
1046
}
1047

    
1048
const char *janus_videoroom_get_package(void) {
1049
        return JANUS_VIDEOROOM_PACKAGE;
1050
}
1051

    
1052
void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
1053
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1054
                *error = -1;
1055
                return;
1056
        }        
1057
        janus_videoroom_session *session = (janus_videoroom_session *)g_malloc0(sizeof(janus_videoroom_session));
1058
        session->handle = handle;
1059
        session->participant_type = janus_videoroom_p_type_none;
1060
        session->participant = NULL;
1061
        session->destroyed = 0;
1062
        g_atomic_int_set(&session->hangingup, 0);
1063
        handle->plugin_handle = session;
1064
        janus_mutex_lock(&sessions_mutex);
1065
        g_hash_table_insert(sessions, handle, session);
1066
        janus_mutex_unlock(&sessions_mutex);
1067

    
1068
        return;
1069
}
1070

    
1071
static void janus_videoroom_notify_participants(janus_videoroom_participant *participant, json_t *msg) {
1072
        /* participant->room->participants_mutex has to be locked. */
1073
        GHashTableIter iter;
1074
        gpointer value;
1075
        g_hash_table_iter_init(&iter, participant->room->participants);
1076
        while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1077
                janus_videoroom_participant *p = value;
1078
                if(p && p->session && p != participant) {
1079
                        JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1080
                        int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, msg, NULL);
1081
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1082
                }
1083
        }
1084
}
1085

    
1086
static void janus_videoroom_leave_or_unpublish(janus_videoroom_participant *participant, gboolean is_leaving, gboolean kicked) {
1087
        /* we need to check if the room still exists, may have been destroyed already */
1088
        if(participant->room && !participant->room->destroyed) {
1089
                json_t *event = json_object();
1090
                json_object_set_new(event, "videoroom", json_string("event"));
1091
                json_object_set_new(event, "room", json_integer(participant->room->room_id));
1092
                json_object_set_new(event, is_leaving ? (kicked ? "kicked" : "leaving") : "unpublished",
1093
                        json_integer(participant->user_id));
1094
                janus_mutex_lock(&participant->room->participants_mutex);
1095
                janus_videoroom_notify_participants(participant, event);
1096
                if(is_leaving) {
1097
                        g_hash_table_remove(participant->room->participants, &participant->user_id);
1098
                        g_hash_table_remove(participant->room->private_ids, GUINT_TO_POINTER(participant->pvt_id));
1099
                }
1100
                janus_mutex_unlock(&participant->room->participants_mutex);
1101
                json_decref(event);
1102
        }
1103
}
1104

    
1105
void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
1106
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1107
                *error = -1;
1108
                return;
1109
        }        
1110
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle; 
1111
        if(!session) {
1112
                JANUS_LOG(LOG_ERR, "No VideoRoom session associated with this handle...\n");
1113
                *error = -2;
1114
                return;
1115
        }
1116
        if(session->destroyed) {
1117
                JANUS_LOG(LOG_WARN, "VideoRoom session already marked as destroyed...\n");
1118
                return;
1119
        }
1120
        JANUS_LOG(LOG_VERB, "Removing VideoRoom session...\n");
1121
        /* Cleaning up and removing the session is done in a lazy way */
1122
        janus_mutex_lock(&sessions_mutex);
1123
        if(!session->destroyed) {
1124
                /* Any related WebRTC PeerConnection is not available anymore either */
1125
                janus_videoroom_hangup_media(handle);
1126
                session->destroyed = janus_get_monotonic_time();
1127
                old_sessions = g_list_append(old_sessions, session);
1128
                if(session->participant_type == janus_videoroom_p_type_publisher) {
1129
                        /* Get rid of publisher */
1130
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1131
                        participant->audio = FALSE;
1132
                        participant->video = FALSE;
1133
                        participant->data = FALSE;
1134
                        participant->audio_active = FALSE;
1135
                        participant->video_active = FALSE;
1136
                        participant->data_active = FALSE;
1137
                        participant->recording_active = FALSE;
1138
                        if(participant->recording_base)
1139
                                g_free(participant->recording_base);
1140
                        participant->recording_base = NULL;
1141
                        session->participant_type = janus_videoroom_p_type_none;
1142
                        janus_videoroom_leave_or_unpublish(participant, TRUE, FALSE);
1143
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1144
                        /* Detaching this listener from its publisher is already done by hangup_media */
1145
                }
1146
        }
1147
        janus_mutex_unlock(&sessions_mutex);
1148

    
1149
        return;
1150
}
1151

    
1152
json_t *janus_videoroom_query_session(janus_plugin_session *handle) {
1153
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1154
                return NULL;
1155
        }        
1156
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1157
        if(!session) {
1158
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1159
                return NULL;
1160
        }
1161
        /* Show the participant/room info, if any */
1162
        json_t *info = json_object();
1163
        if(session->participant) {
1164
                if(session->participant_type == janus_videoroom_p_type_none) {
1165
                        json_object_set_new(info, "type", json_string("none"));
1166
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
1167
                        json_object_set_new(info, "type", json_string("publisher"));
1168
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1169
                        if(participant) {
1170
                                janus_videoroom *room = participant->room; 
1171
                                json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1172
                                json_object_set_new(info, "id", json_integer(participant->user_id));
1173
                                json_object_set_new(info, "private_id", json_integer(participant->pvt_id));
1174
                                if(participant->display)
1175
                                        json_object_set_new(info, "display", json_string(participant->display));
1176
                                if(participant->listeners)
1177
                                        json_object_set_new(info, "viewers", json_integer(g_slist_length(participant->listeners)));
1178
                                json_t *media = json_object();
1179
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1180
                                json_object_set_new(media, "video", json_integer(participant->video));
1181
                                json_object_set_new(media, "data", json_integer(participant->data));
1182
                                json_object_set_new(info, "media", media);
1183
                                json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
1184
                                if(participant->arc || participant->vrc || participant->drc) {
1185
                                        json_t *recording = json_object();
1186
                                        if(participant->arc && participant->arc->filename)
1187
                                                json_object_set_new(recording, "audio", json_string(participant->arc->filename));
1188
                                        if(participant->vrc && participant->vrc->filename)
1189
                                                json_object_set_new(recording, "video", json_string(participant->vrc->filename));
1190
                                        if(participant->drc && participant->drc->filename)
1191
                                                json_object_set_new(recording, "data", json_string(participant->drc->filename));
1192
                                        json_object_set_new(info, "recording", recording);
1193
                                }
1194
                                if(participant->audio_level_extmap_id > 0) {
1195
                                        json_object_set_new(info, "audio-level-dBov", json_integer(participant->audio_dBov_level));
1196
                                        json_object_set_new(info, "talking", participant->talking ? json_true() : json_false());
1197
                                }
1198
                        }
1199
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1200
                        json_object_set_new(info, "type", json_string("listener"));
1201
                        janus_videoroom_listener *participant = (janus_videoroom_listener *)session->participant;
1202
                        if(participant) {
1203
                                janus_videoroom_participant *feed = (janus_videoroom_participant *)participant->feed;
1204
                                if(feed) {
1205
                                        janus_videoroom *room = feed->room; 
1206
                                        json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1207
                                        json_object_set_new(info, "private_id", json_integer(participant->pvt_id));
1208
                                        json_object_set_new(info, "feed_id", json_integer(feed->user_id));
1209
                                        if(feed->display)
1210
                                                json_object_set_new(info, "feed_display", json_string(feed->display));
1211
                                }
1212
                                json_t *media = json_object();
1213
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1214
                                json_object_set_new(media, "video", json_integer(participant->video));
1215
                                json_object_set_new(media, "data", json_integer(participant->data));
1216
                                json_object_set_new(info, "media", media);
1217
                                if(participant->room && participant->room->do_svc) {
1218
                                        json_t *svc = json_object();
1219
                                        json_object_set_new(svc, "spatial-layer", json_integer(participant->spatial_layer));
1220
                                        json_object_set_new(svc, "target-spatial-layer", json_integer(participant->target_spatial_layer));
1221
                                        json_object_set_new(svc, "temporal-layer", json_integer(participant->temporal_layer));
1222
                                        json_object_set_new(svc, "target-temporal-layer", json_integer(participant->target_temporal_layer));
1223
                                        json_object_set_new(info, "svc", svc);
1224
                                }
1225
                        }
1226
                }
1227
        }
1228
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
1229
        return info;
1230
}
1231

    
1232
static int janus_videoroom_access_room(json_t *root, gboolean check_secret, gboolean check_pin, janus_videoroom **videoroom, char *error_cause, int error_cause_size) {
1233
        /* rooms_mutex has to be locked */
1234
        int error_code = 0;
1235
        json_t *room = json_object_get(root, "room");
1236
        guint64 room_id = json_integer_value(room);
1237
        *videoroom = g_hash_table_lookup(rooms, &room_id);
1238
        if(*videoroom == NULL) {
1239
                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1240
                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1241
                if(error_cause)
1242
                        g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
1243
                return error_code;
1244
        }
1245
        if((*videoroom)->destroyed) {
1246
                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1247
                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1248
                if(error_cause)
1249
                        g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
1250
                return error_code;
1251
        }
1252
        if(check_secret) {
1253
                char error_cause2[100];
1254
                JANUS_CHECK_SECRET((*videoroom)->room_secret, root, "secret", error_code, error_cause2,
1255
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1256
                if(error_code != 0) {
1257
                        g_strlcpy(error_cause, error_cause2, error_cause_size);
1258
                        return error_code;
1259
                }
1260
        }
1261
        if(check_pin) {
1262
                char error_cause2[100];
1263
                JANUS_CHECK_SECRET((*videoroom)->room_pin, root, "pin", error_code, error_cause2,
1264
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1265
                if(error_code != 0) {
1266
                        g_strlcpy(error_cause, error_cause2, error_cause_size);
1267
                        return error_code;
1268
                }
1269
        }
1270
        return 0;
1271
}
1272

    
1273
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
1274
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1275
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
1276
        
1277
        /* Pre-parse the message */
1278
        int error_code = 0;
1279
        char error_cause[512];
1280
        json_t *root = message;
1281
        json_t *response = NULL;
1282

    
1283
        if(message == NULL) {
1284
                JANUS_LOG(LOG_ERR, "No message??\n");
1285
                error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
1286
                g_snprintf(error_cause, 512, "%s", "No message??");
1287
                goto plugin_response;
1288
        }
1289

    
1290
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1291
        if(!session) {
1292
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1293
                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1294
                g_snprintf(error_cause, 512, "%s", "session associated with this handle...");
1295
                goto plugin_response;
1296
        }
1297
        if(session->destroyed) {
1298
                JANUS_LOG(LOG_ERR, "Session has already been marked as destroyed...\n");
1299
                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1300
                g_snprintf(error_cause, 512, "%s", "Session has already been marked as destroyed...");
1301
                goto plugin_response;
1302
        }
1303
        if(!json_is_object(root)) {
1304
                JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1305
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
1306
                g_snprintf(error_cause, 512, "JSON error: not an object");
1307
                goto plugin_response;
1308
        }
1309
        /* Get the request first */
1310
        JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
1311
                error_code, error_cause, TRUE,
1312
                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1313
        if(error_code != 0)
1314
                goto plugin_response;
1315
        json_t *request = json_object_get(root, "request");
1316
        /* Some requests ('create', 'destroy', 'exists', 'list') can be handled synchronously */
1317
        const char *request_text = json_string_value(request);
1318
        if(!strcasecmp(request_text, "create")) {
1319
                /* Create a new videoroom */
1320
                JANUS_LOG(LOG_VERB, "Creating a new videoroom\n");
1321
                JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
1322
                        error_code, error_cause, TRUE,
1323
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1324
                if(error_code != 0)
1325
                        goto plugin_response;
1326
                if(admin_key != NULL) {
1327
                        /* An admin key was specified: make sure it was provided, and that it's valid */
1328
                        JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
1329
                                error_code, error_cause, TRUE,
1330
                                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1331
                        if(error_code != 0)
1332
                                goto plugin_response;
1333
                        JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
1334
                                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1335
                        if(error_code != 0)
1336
                                goto plugin_response;
1337
                }
1338
                json_t *desc = json_object_get(root, "description");
1339
                json_t *is_private = json_object_get(root, "is_private");
1340
                json_t *req_pvtid = json_object_get(root, "require_pvtid");
1341
                json_t *secret = json_object_get(root, "secret");
1342
                json_t *pin = json_object_get(root, "pin");
1343
                json_t *bitrate = json_object_get(root, "bitrate");
1344
                json_t *fir_freq = json_object_get(root, "fir_freq");
1345
                json_t *publishers = json_object_get(root, "publishers");
1346
                json_t *allowed = json_object_get(root, "allowed");
1347
                json_t *audiocodec = json_object_get(root, "audiocodec");
1348
                if(audiocodec) {
1349
                        const char *audiocodec_value = json_string_value(audiocodec);
1350
                        if(!strcasecmp(audiocodec_value, "opus") && !strcasecmp(audiocodec_value, "g722") &&
1351
                                        !strcasecmp(audiocodec_value, "isac32") && !strcasecmp(audiocodec_value, "isac16") &&
1352
                                        !strcasecmp(audiocodec_value, "pcmu") && !strcasecmp(audiocodec_value, "pcma")) {
1353
                                JANUS_LOG(LOG_ERR, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, pcma or g722)\n");
1354
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1355
                                g_snprintf(error_cause, 512, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, pcma or g722)");
1356
                                goto plugin_response;
1357
                        }
1358
                }
1359
                json_t *videocodec = json_object_get(root, "videocodec");
1360
                if(videocodec) {
1361
                        const char *videocodec_value = json_string_value(videocodec);
1362
                        if(!strcasecmp(videocodec_value, "vp8") && !strcasecmp(videocodec_value, "vp9") && !strcasecmp(videocodec_value, "h264")) {
1363
                                JANUS_LOG(LOG_ERR, "Invalid element (videocodec can only be vp8, vp9 or h264)\n");
1364
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1365
                                g_snprintf(error_cause, 512, "Invalid element (videocodec can only be vp8, vp9 or h264)");
1366
                                goto plugin_response;
1367
                        }
1368
                }
1369
                json_t *svc = json_object_get(root, "video_svc");
1370
                json_t *audiolevel_ext = json_object_get(root, "audiolevel_ext");
1371
                json_t *audiolevel_event = json_object_get(root, "audiolevel_event");
1372
                json_t *audio_active_packets = json_object_get(root, "audio_active_packets");
1373
                json_t *audio_level_average = json_object_get(root, "audio_level_average");
1374
                json_t *videoorient_ext = json_object_get(root, "videoorient_ext");
1375
                json_t *playoutdelay_ext = json_object_get(root, "playoutdelay_ext");
1376
                json_t *record = json_object_get(root, "record");
1377
                json_t *rec_dir = json_object_get(root, "rec_dir");
1378
                json_t *permanent = json_object_get(root, "permanent");
1379
                if(allowed) {
1380
                        /* Make sure the "allowed" array only contains strings */
1381
                        gboolean ok = TRUE;
1382
                        if(json_array_size(allowed) > 0) {
1383
                                size_t i = 0;
1384
                                for(i=0; i<json_array_size(allowed); i++) {
1385
                                        json_t *a = json_array_get(allowed, i);
1386
                                        if(!a || !json_is_string(a)) {
1387
                                                ok = FALSE;
1388
                                                break;
1389
                                        }
1390
                                }
1391
                        }
1392
                        if(!ok) {
1393
                                JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
1394
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1395
                                g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
1396
                                goto plugin_response;
1397
                        }
1398
                }
1399
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1400
                if(save && config == NULL) {
1401
                        JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
1402
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1403
                        g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
1404
                        goto plugin_response;
1405
                }
1406
                guint64 room_id = 0;
1407
                json_t *room = json_object_get(root, "room");
1408
                if(room) {
1409
                        room_id = json_integer_value(room);
1410
                        if(room_id == 0) {
1411
                                JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
1412
                        }
1413
                }
1414
                janus_mutex_lock(&rooms_mutex);
1415
                if(room_id > 0) {
1416
                        /* Let's make sure the room doesn't exist already */
1417
                        if(g_hash_table_lookup(rooms, &room_id) != NULL) {
1418
                                /* It does... */
1419
                                janus_mutex_unlock(&rooms_mutex);
1420
                                JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
1421
                                error_code = JANUS_VIDEOROOM_ERROR_ROOM_EXISTS;
1422
                                g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
1423
                                goto plugin_response;
1424
                        }
1425
                }
1426
                /* Create the room */
1427
                janus_videoroom *videoroom = g_malloc0(sizeof(janus_videoroom));
1428
                /* Generate a random ID */
1429
                if(room_id == 0) {
1430
                        while(room_id == 0) {
1431
                                room_id = janus_random_uint64();
1432
                                if(g_hash_table_lookup(rooms, &room_id) != NULL) {
1433
                                        /* Room ID already taken, try another one */
1434
                                        room_id = 0;
1435
                                }
1436
                        }
1437
                }
1438
                videoroom->room_id = room_id;
1439
                char *description = NULL;
1440
                if(desc != NULL && strlen(json_string_value(desc)) > 0) {
1441
                        description = g_strdup(json_string_value(desc));
1442
                } else {
1443
                        char roomname[255];
1444
                        g_snprintf(roomname, 255, "Room %"SCNu64"", videoroom->room_id);
1445
                        description = g_strdup(roomname);
1446
                }
1447
                videoroom->room_name = description;
1448
                videoroom->is_private = is_private ? json_is_true(is_private) : FALSE;
1449
                videoroom->require_pvtid = req_pvtid ? json_is_true(req_pvtid) : FALSE;
1450
                if(secret)
1451
                        videoroom->room_secret = g_strdup(json_string_value(secret));
1452
                if(pin)
1453
                        videoroom->room_pin = g_strdup(json_string_value(pin));
1454
                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
1455
                if(publishers)
1456
                        videoroom->max_publishers = json_integer_value(publishers);
1457
                if(videoroom->max_publishers < 0)
1458
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
1459
                videoroom->bitrate = 0;
1460
                if(bitrate)
1461
                        videoroom->bitrate = json_integer_value(bitrate);
1462
                if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
1463
                        videoroom->bitrate = 64000;        /* Don't go below 64k */
1464
                videoroom->fir_freq = 0;
1465
                if(fir_freq)
1466
                        videoroom->fir_freq = json_integer_value(fir_freq);
1467
                videoroom->acodec = JANUS_VIDEOROOM_OPUS;
1468
                if(audiocodec) {
1469
                        const char *audiocodec_value = json_string_value(audiocodec);
1470
                        if(!strcasecmp(audiocodec_value, "opus"))
1471
                                videoroom->acodec = JANUS_VIDEOROOM_OPUS;
1472
                        else if(!strcasecmp(audiocodec_value, "isac32"))
1473
                                videoroom->acodec = JANUS_VIDEOROOM_ISAC_32K;
1474
                        else if(!strcasecmp(audiocodec_value, "isac16"))
1475
                                videoroom->acodec = JANUS_VIDEOROOM_ISAC_16K;
1476
                        else if(!strcasecmp(audiocodec_value, "pcmu"))
1477
                                videoroom->acodec = JANUS_VIDEOROOM_PCMU;
1478
                        else if(!strcasecmp(audiocodec_value, "pcma"))
1479
                                videoroom->acodec = JANUS_VIDEOROOM_PCMA;
1480
                        else if(!strcasecmp(audiocodec_value, "g722"))
1481
                                videoroom->acodec = JANUS_VIDEOROOM_G722;
1482
                        else {
1483
                                JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s', falling back to OPUS\n", audiocodec_value);
1484
                                videoroom->acodec = JANUS_VIDEOROOM_OPUS;
1485
                        }
1486
                }
1487
                videoroom->vcodec = JANUS_VIDEOROOM_VP8;
1488
                if(videocodec) {
1489
                        const char *videocodec_value = json_string_value(videocodec);
1490
                        if(!strcasecmp(videocodec_value, "vp8"))
1491
                                videoroom->vcodec = JANUS_VIDEOROOM_VP8;
1492
                        else if(!strcasecmp(videocodec_value, "vp9"))
1493
                                videoroom->vcodec = JANUS_VIDEOROOM_VP9;
1494
                        else if(!strcasecmp(videocodec_value, "h264"))
1495
                                videoroom->vcodec = JANUS_VIDEOROOM_H264;
1496
                        else {
1497
                                JANUS_LOG(LOG_WARN, "Unsupported video codec '%s', falling back to VP8\n", videocodec_value);
1498
                                videoroom->vcodec = JANUS_VIDEOROOM_VP8;
1499
                        }
1500
                }
1501
                if(svc && json_is_true(svc)) {
1502
                        if(videoroom->vcodec == JANUS_VIDEOROOM_VP9) {
1503
                                videoroom->do_svc = TRUE;
1504
                        } else {
1505
                                JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9, not %s: disabling it...\n",
1506
                                        janus_videoroom_videocodec_name(videoroom->vcodec));
1507
                        }
1508
                }
1509
                videoroom->audiolevel_ext = audiolevel_ext ? json_is_true(audiolevel_ext) : TRUE;
1510
                videoroom->audiolevel_event = audiolevel_event ? json_is_true(audiolevel_event) : FALSE;
1511
                if(videoroom->audiolevel_event) {
1512
                        videoroom->audio_active_packets = 100;
1513
                        if(json_integer_value(audio_active_packets) > 0) {
1514
                                videoroom->audio_active_packets = json_integer_value(audio_active_packets);
1515
                        } else {
1516
                                JANUS_LOG(LOG_WARN, "Invalid audio_active_packets value provided, using default: %d\n", videoroom->audio_active_packets);
1517
                        }
1518
                        videoroom->audio_level_average = 25;
1519
                        if(json_integer_value(audio_level_average) > 0) {
1520
                                videoroom->audio_level_average = json_integer_value(audio_level_average);
1521
                        } else {
1522
                                JANUS_LOG(LOG_WARN, "Invalid audio_level_average value provided, using default: %d\n", videoroom->audio_level_average);
1523
                        }
1524
                }
1525
                videoroom->videoorient_ext = videoorient_ext ? json_is_true(videoorient_ext) : TRUE;
1526
                videoroom->playoutdelay_ext = playoutdelay_ext ? json_is_true(playoutdelay_ext) : TRUE;
1527
                if(record) {
1528
                        videoroom->record = json_is_true(record);
1529
                }
1530
                if(rec_dir) {
1531
                        videoroom->rec_dir = g_strdup(json_string_value(rec_dir));
1532
                }
1533
                videoroom->destroyed = 0;
1534
                janus_mutex_init(&videoroom->participants_mutex);
1535
                videoroom->participants = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
1536
                videoroom->private_ids = g_hash_table_new(NULL, NULL);
1537
                videoroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
1538
                if(allowed != NULL) {
1539
                        /* Populate the "allowed" list as an ACL for people trying to join */
1540
                        if(json_array_size(allowed) > 0) {
1541
                                size_t i = 0;
1542
                                for(i=0; i<json_array_size(allowed); i++) {
1543
                                        const char *token = json_string_value(json_array_get(allowed, i));
1544
                                        if(!g_hash_table_lookup(videoroom->allowed, token))
1545
                                                g_hash_table_insert(videoroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
1546
                                }
1547
                        }
1548
                        videoroom->check_tokens = TRUE;
1549
                }
1550
                JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, %s/%s codecs, secret: %s, pin: %s, pvtid: %s)\n",
1551
                        videoroom->room_id, videoroom->room_name,
1552
                        videoroom->is_private ? "private" : "public",
1553
                        janus_videoroom_audiocodec_name(videoroom->acodec),
1554
                        janus_videoroom_videocodec_name(videoroom->vcodec),
1555
                        videoroom->room_secret ? videoroom->room_secret : "no secret",
1556
                        videoroom->room_pin ? videoroom->room_pin : "no pin",
1557
                        videoroom->require_pvtid ? "required" : "optional");
1558
                if(videoroom->record) {
1559
                        JANUS_LOG(LOG_VERB, "  -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
1560
                }
1561
                if(save) {
1562
                        /* This room is permanent: save to the configuration file too
1563
                         * FIXME: We should check if anything fails... */
1564
                        JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", videoroom->room_id);
1565
                        janus_mutex_lock(&config_mutex);
1566
                        char cat[BUFSIZ], value[BUFSIZ];
1567
                        /* The room ID is the category */
1568
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, videoroom->room_id);
1569
                        janus_config_add_category(config, cat);
1570
                        /* Now for the values */
1571
                        janus_config_add_item(config, cat, "description", videoroom->room_name);
1572
                        if(videoroom->is_private)
1573
                                janus_config_add_item(config, cat, "is_private", "yes");
1574
                        if(videoroom->require_pvtid)
1575
                                janus_config_add_item(config, cat, "require_pvtid", "yes");
1576
                        g_snprintf(value, BUFSIZ, "%"SCNu64, videoroom->bitrate);
1577
                        janus_config_add_item(config, cat, "bitrate", value);
1578
                        g_snprintf(value, BUFSIZ, "%d", videoroom->max_publishers);
1579
                        janus_config_add_item(config, cat, "publishers", value);
1580
                        if(videoroom->fir_freq) {
1581
                                g_snprintf(value, BUFSIZ, "%"SCNu16, videoroom->fir_freq);
1582
                                janus_config_add_item(config, cat, "fir_freq", value);
1583
                        }
1584
                        janus_config_add_item(config, cat, "audiocodec", janus_videoroom_audiocodec_name(videoroom->acodec));
1585
                        janus_config_add_item(config, cat, "videocodec", janus_videoroom_videocodec_name(videoroom->vcodec));
1586
                        if(videoroom->do_svc)
1587
                                janus_config_add_item(config, cat, "video_svc", "yes");
1588
                        if(videoroom->room_secret)
1589
                                janus_config_add_item(config, cat, "secret", videoroom->room_secret);
1590
                        if(videoroom->room_pin)
1591
                                janus_config_add_item(config, cat, "pin", videoroom->room_pin);
1592
                        if(videoroom->record)
1593
                                janus_config_add_item(config, cat, "record", "yes");
1594
                        if(videoroom->rec_dir)
1595
                                janus_config_add_item(config, cat, "rec_dir", videoroom->rec_dir);
1596
                        /* Save modified configuration */
1597
                        if(janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE) < 0)
1598
                                save = FALSE;        /* This will notify the user the room is not permanent */
1599
                        janus_mutex_unlock(&config_mutex);
1600
                }
1601
                /* Show updated rooms list */
1602
                GHashTableIter iter;
1603
                gpointer value;
1604
                g_hash_table_insert(rooms, janus_uint64_dup(videoroom->room_id), videoroom);
1605
                g_hash_table_iter_init(&iter, rooms);
1606
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1607
                        janus_videoroom *vr = value;
1608
                        JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
1609
                }
1610
                janus_mutex_unlock(&rooms_mutex);
1611
                /* Send info back */
1612
                response = json_object();
1613
                json_object_set_new(response, "videoroom", json_string("created"));
1614
                json_object_set_new(response, "room", json_integer(videoroom->room_id));
1615
                json_object_set_new(response, "permanent", save ? json_true() : json_false());
1616
                /* Also notify event handlers */
1617
                if(notify_events && gateway->events_is_enabled()) {
1618
                        json_t *info = json_object();
1619
                        json_object_set_new(info, "event", json_string("created"));
1620
                        json_object_set_new(info, "room", json_integer(videoroom->room_id));
1621
                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
1622
                }
1623
                goto plugin_response;
1624
        } else if(!strcasecmp(request_text, "destroy")) {
1625
                JANUS_LOG(LOG_VERB, "Attempt to destroy an existing videoroom room\n");
1626
                JANUS_VALIDATE_JSON_OBJECT(root, destroy_parameters,
1627
                        error_code, error_cause, TRUE,
1628
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1629
                if(error_code != 0)
1630
                        goto plugin_response;
1631
                json_t *room = json_object_get(root, "room");
1632
                json_t *permanent = json_object_get(root, "permanent");
1633
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1634
                if(save && config == NULL) {
1635
                        JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
1636
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1637
                        g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
1638
                        goto plugin_response;
1639
                }
1640
                guint64 room_id = json_integer_value(room);
1641
                janus_mutex_lock(&rooms_mutex);
1642
                janus_videoroom *videoroom = NULL;
1643
                error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
1644
                if(error_code != 0) {
1645
                        janus_mutex_unlock(&rooms_mutex);
1646
                        goto plugin_response;
1647
                }
1648
                /* Notify all participants that the fun is over, and that they'll be kicked */
1649
                JANUS_LOG(LOG_VERB, "Notifying all participants\n");
1650
                json_t *destroyed = json_object();
1651
                json_object_set_new(destroyed, "videoroom", json_string("destroyed"));
1652
                json_object_set_new(destroyed, "room", json_integer(videoroom->room_id));
1653
                GHashTableIter iter;
1654
                gpointer value;
1655
                /* Remove room lazily*/
1656
                videoroom->destroyed = janus_get_monotonic_time();
1657
                old_rooms = g_list_append(old_rooms, videoroom);
1658
                janus_mutex_lock(&videoroom->participants_mutex);
1659
                g_hash_table_iter_init(&iter, videoroom->participants);
1660
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1661
                        janus_videoroom_participant *p = value;
1662
                        if(p && p->session) {
1663
                                /* Notify the user we're going to destroy the room... */
1664
                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, destroyed, NULL);
1665
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1666
                                /* ... and then ask the core to remove the handle */
1667
                                gateway->end_session(p->session->handle);
1668
                        }
1669
                }
1670
                json_decref(destroyed);
1671
                janus_mutex_unlock(&videoroom->participants_mutex);
1672
                /* Also notify event handlers */
1673
                if(notify_events && gateway->events_is_enabled()) {
1674
                        json_t *info = json_object();
1675
                        json_object_set_new(info, "event", json_string("destroyed"));
1676
                        json_object_set_new(info, "room", json_integer(room_id));
1677
                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
1678
                }
1679
                janus_mutex_unlock(&rooms_mutex);
1680
                if(save) {
1681
                        /* This change is permanent: save to the configuration file too
1682
                         * FIXME: We should check if anything fails... */
1683
                        JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id);
1684
                        janus_mutex_lock(&config_mutex);
1685
                        char cat[BUFSIZ];
1686
                        /* The room ID is the category */
1687
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, room_id);
1688
                        janus_config_remove_category(config, cat);
1689
                        /* Save modified configuration */
1690
                        janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE);
1691
                        janus_mutex_unlock(&config_mutex);
1692
                }
1693
                /* Done */
1694
                response = json_object();
1695
                json_object_set_new(response, "videoroom", json_string("destroyed"));
1696
                json_object_set_new(response, "room", json_integer(room_id));
1697
                goto plugin_response;
1698
        } else if(!strcasecmp(request_text, "list")) {
1699
                /* List all rooms (but private ones) and their details (except for the secret, of course...) */
1700
                json_t *list = json_array();
1701
                JANUS_LOG(LOG_VERB, "Getting the list of video rooms\n");
1702
                janus_mutex_lock(&rooms_mutex);
1703
                GHashTableIter iter;
1704
                gpointer value;
1705
                g_hash_table_iter_init(&iter, rooms);
1706
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1707
                        janus_videoroom *room = value;
1708
                        if(!room)
1709
                                continue;
1710
                        if(room->is_private) {
1711
                                /* Skip private room */
1712
                                JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
1713
                                continue;
1714
                        }
1715
                        if(!room->destroyed) {
1716
                                json_t *rl = json_object();
1717
                                json_object_set_new(rl, "room", json_integer(room->room_id));
1718
                                json_object_set_new(rl, "description", json_string(room->room_name));
1719
                                json_object_set_new(rl, "pin_required", room->room_pin ? json_true() : json_false());
1720
                                json_object_set_new(rl, "max_publishers", json_integer(room->max_publishers));
1721
                                json_object_set_new(rl, "bitrate", json_integer(room->bitrate));
1722
                                json_object_set_new(rl, "fir_freq", json_integer(room->fir_freq));
1723
                                json_object_set_new(rl, "audiocodec", json_string(janus_videoroom_audiocodec_name(room->acodec)));
1724
                                json_object_set_new(rl, "videocodec", json_string(janus_videoroom_videocodec_name(room->vcodec)));
1725
                                if(room->do_svc)
1726
                                        json_object_set_new(rl, "video_svc", json_true());
1727
                                json_object_set_new(rl, "record", room->record ? json_true() : json_false());
1728
                                json_object_set_new(rl, "rec_dir", json_string(room->rec_dir));
1729
                                /* TODO: Should we list participants as well? or should there be a separate API call on a specific room for this? */
1730
                                json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
1731
                                json_array_append_new(list, rl);
1732
                        }
1733
                }
1734
                janus_mutex_unlock(&rooms_mutex);
1735
                response = json_object();
1736
                json_object_set_new(response, "videoroom", json_string("success"));
1737
                json_object_set_new(response, "list", list);
1738
                goto plugin_response;
1739
        } else if(!strcasecmp(request_text, "rtp_forward")) {
1740
                JANUS_VALIDATE_JSON_OBJECT(root, rtp_forward_parameters,
1741
                        error_code, error_cause, TRUE,
1742
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1743
                if(error_code != 0)
1744
                        goto plugin_response;
1745
                json_t *room = json_object_get(root, "room");
1746
                json_t *pub_id = json_object_get(root, "publisher_id");
1747
                int video_port = -1, video_pt = 0;
1748
                uint32_t video_ssrc = 0;
1749
                int audio_port = -1, audio_pt = 0;
1750
                uint32_t audio_ssrc = 0;
1751
                int data_port = -1;
1752
                json_t *vid_port = json_object_get(root, "video_port");
1753
                if(vid_port) {
1754
                        video_port = json_integer_value(vid_port);
1755
                        json_t *pt = json_object_get(root, "video_pt");
1756
                        if(pt)
1757
                                video_pt = json_integer_value(pt);
1758
                        json_t *ssrc = json_object_get(root, "video_ssrc");
1759
                        if(ssrc)
1760
                                video_ssrc = json_integer_value(ssrc);
1761
                }
1762
                json_t *au_port = json_object_get(root, "audio_port");
1763
                if(au_port) {
1764
                        audio_port = json_integer_value(au_port);
1765
                        json_t *pt = json_object_get(root, "audio_pt");
1766
                        if(pt)
1767
                                audio_pt = json_integer_value(pt);
1768
                        json_t *ssrc = json_object_get(root, "audio_ssrc");
1769
                        if(ssrc)
1770
                                audio_ssrc = json_integer_value(ssrc);
1771
                }
1772
                json_t *d_port = json_object_get(root, "data_port");
1773
                if(d_port) {
1774
                        data_port = json_integer_value(d_port);
1775
                }
1776
                json_t *json_host = json_object_get(root, "host");
1777
                
1778
                guint64 room_id = json_integer_value(room);
1779
                guint64 publisher_id = json_integer_value(pub_id);
1780
                const gchar* host = json_string_value(json_host);
1781
                janus_mutex_lock(&rooms_mutex);
1782
                janus_videoroom *videoroom = NULL;
1783
                error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
1784
                janus_mutex_unlock(&rooms_mutex);
1785
                if(error_code != 0)
1786
                        goto plugin_response;
1787
                janus_mutex_lock(&videoroom->participants_mutex);
1788
                janus_videoroom_participant* publisher = g_hash_table_lookup(videoroom->participants, &publisher_id);
1789
                if(publisher == NULL) {
1790
                        janus_mutex_unlock(&videoroom->participants_mutex);
1791
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1792
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1793
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1794
                        goto plugin_response;
1795
                }
1796
                if(publisher->udp_sock <= 0) {
1797
                        publisher->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
1798
                        if(publisher->udp_sock <= 0) {
1799
                                janus_mutex_unlock(&videoroom->participants_mutex);
1800
                                JANUS_LOG(LOG_ERR, "Could not open UDP socket for rtp stream for publisher (%"SCNu64")\n", publisher_id);
1801
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1802
                                g_snprintf(error_cause, 512, "Could not open UDP socket for rtp stream");
1803
                                goto plugin_response;
1804
                        }
1805
                }
1806
                guint32 audio_handle = 0;
1807
                guint32 video_handle = 0;
1808
                guint32 data_handle = 0;
1809
                if(audio_port > 0) {
1810
                        audio_handle = janus_videoroom_rtp_forwarder_add_helper(publisher, host, audio_port, audio_pt, audio_ssrc, FALSE, FALSE);
1811
                }
1812
                if(video_port > 0) {
1813
                        video_handle = janus_videoroom_rtp_forwarder_add_helper(publisher, host, video_port, video_pt, video_ssrc, TRUE, FALSE);
1814
                }
1815
                if(data_port > 0) {
1816
                        data_handle = janus_videoroom_rtp_forwarder_add_helper(publisher, host, data_port, 0, 0, FALSE, TRUE);
1817
                }
1818
                janus_mutex_unlock(&videoroom->participants_mutex);
1819
                response = json_object();
1820
                json_t* rtp_stream = json_object();
1821
                if(audio_handle > 0) {
1822
                        json_object_set_new(rtp_stream, "audio_stream_id", json_integer(audio_handle));
1823
                        json_object_set_new(rtp_stream, "audio", json_integer(audio_port));
1824
                }
1825
                if(video_handle > 0) {
1826
                        /* Send a FIR to the new RTP forward publisher */
1827
                        char buf[20];
1828
                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
1829
                        JANUS_LOG(LOG_VERB, "New RTP forward publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
1830
                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
1831
                        /* Send a PLI too, just in case... */
1832
                        janus_rtcp_pli((char *)&buf, 12);
1833
                        JANUS_LOG(LOG_VERB, "New RTP forward publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
1834
                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
1835
                        /* Done */
1836
                        json_object_set_new(rtp_stream, "video_stream_id", json_integer(video_handle));
1837
                        json_object_set_new(rtp_stream, "video", json_integer(video_port));
1838
                }
1839
                if(data_handle > 0) {
1840
                        json_object_set_new(rtp_stream, "data_stream_id", json_integer(data_handle));
1841
                        json_object_set_new(rtp_stream, "data", json_integer(data_port));
1842
                }
1843
                json_object_set_new(rtp_stream, "host", json_string(host));
1844
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1845
                json_object_set_new(response, "rtp_stream", rtp_stream);
1846
                json_object_set_new(response, "room", json_integer(room_id));
1847
                json_object_set_new(response, "videoroom", json_string("rtp_forward"));
1848
                goto plugin_response;
1849
        } else if(!strcasecmp(request_text, "stop_rtp_forward")) {
1850
                JANUS_VALIDATE_JSON_OBJECT(root, stop_rtp_forward_parameters,
1851
                        error_code, error_cause, TRUE,
1852
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1853
                if(error_code != 0)
1854
                        goto plugin_response;
1855
                json_t *room = json_object_get(root, "room");
1856
                json_t *pub_id = json_object_get(root, "publisher_id");
1857
                json_t *id = json_object_get(root, "stream_id");
1858

    
1859
                guint64 room_id = json_integer_value(room);
1860
                guint64 publisher_id = json_integer_value(pub_id);
1861
                guint32 stream_id = json_integer_value(id);
1862
                janus_mutex_lock(&rooms_mutex);
1863
                janus_videoroom *videoroom = NULL;
1864
                error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
1865
                janus_mutex_unlock(&rooms_mutex);
1866
                if(error_code != 0)
1867
                        goto plugin_response;
1868
                janus_mutex_lock(&videoroom->participants_mutex);
1869
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &publisher_id);
1870
                if(publisher == NULL) {
1871
                        janus_mutex_unlock(&videoroom->participants_mutex);
1872
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1873
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1874
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1875
                        goto plugin_response;
1876
                }
1877
                janus_mutex_lock(&publisher->rtp_forwarders_mutex);
1878
                if(g_hash_table_lookup(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id)) == NULL) {
1879
                        janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1880
                        janus_mutex_unlock(&videoroom->participants_mutex);
1881
                        JANUS_LOG(LOG_ERR, "No such stream (%"SCNu32")\n", stream_id);
1882
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1883
                        g_snprintf(error_cause, 512, "No such stream (%"SCNu32")", stream_id);
1884
                        goto plugin_response;
1885
                }
1886
                g_hash_table_remove(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id));
1887
                janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1888
                janus_mutex_unlock(&videoroom->participants_mutex);
1889
                response = json_object();
1890
                json_object_set_new(response, "videoroom", json_string("stop_rtp_forward"));
1891
                json_object_set_new(response, "room", json_integer(room_id));
1892
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1893
                json_object_set_new(response, "stream_id", json_integer(stream_id));
1894
                goto plugin_response;
1895
        } else if(!strcasecmp(request_text, "exists")) {
1896
                /* Check whether a given room exists or not, returns true/false */        
1897
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1898
                        error_code, error_cause, TRUE,
1899
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1900
                if(error_code != 0)
1901
                        goto plugin_response;
1902
                json_t *room = json_object_get(root, "room");
1903
                guint64 room_id = json_integer_value(room);
1904
                janus_mutex_lock(&rooms_mutex);
1905
                gboolean room_exists = g_hash_table_contains(rooms, &room_id);
1906
                janus_mutex_unlock(&rooms_mutex);
1907
                response = json_object();
1908
                json_object_set_new(response, "videoroom", json_string("success"));
1909
                json_object_set_new(response, "room", json_integer(room_id));
1910
                json_object_set_new(response, "exists", room_exists ? json_true() : json_false());
1911
                goto plugin_response;
1912
        } else if(!strcasecmp(request_text, "allowed")) {
1913
                JANUS_LOG(LOG_VERB, "Attempt to edit the list of allowed participants in an existing videoroom room\n");
1914
                JANUS_VALIDATE_JSON_OBJECT(root, allowed_parameters,
1915
                        error_code, error_cause, TRUE,
1916
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1917
                if(error_code != 0)
1918
                        goto plugin_response;
1919
                json_t *action = json_object_get(root, "action");
1920
                json_t *room = json_object_get(root, "room");
1921
                json_t *allowed = json_object_get(root, "allowed");
1922
                const char *action_text = json_string_value(action);
1923
                if(strcasecmp(action_text, "enable") && strcasecmp(action_text, "disable") &&
1924
                                strcasecmp(action_text, "add") && strcasecmp(action_text, "remove")) {
1925
                        JANUS_LOG(LOG_ERR, "Unsupported action '%s' (allowed)\n", action_text);
1926
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1927
                        g_snprintf(error_cause, 512, "Unsupported action '%s' (allowed)", action_text);
1928
                        goto plugin_response;
1929
                }
1930
                guint64 room_id = json_integer_value(room);
1931
                janus_mutex_lock(&rooms_mutex);
1932
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
1933
                if(videoroom == NULL) {
1934
                        janus_mutex_unlock(&rooms_mutex);
1935
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1936
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1937
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1938
                        goto plugin_response;
1939
                }
1940
                /* A secret may be required for this action */
1941
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1942
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1943
                if(error_code != 0) {
1944
                        janus_mutex_unlock(&rooms_mutex);
1945
                        goto plugin_response;
1946
                }
1947
                if(!strcasecmp(action_text, "enable")) {
1948
                        JANUS_LOG(LOG_VERB, "Enabling the check on allowed authorization tokens for room %"SCNu64"\n", room_id);
1949
                        videoroom->check_tokens = TRUE;
1950
                } else if(!strcasecmp(action_text, "disable")) {
1951
                        JANUS_LOG(LOG_VERB, "Disabling the check on allowed authorization tokens for room %"SCNu64" (free entry)\n", room_id);
1952
                        videoroom->check_tokens = FALSE;
1953
                } else {
1954
                        gboolean add = !strcasecmp(action_text, "add");
1955
                        if(allowed) {
1956
                                /* Make sure the "allowed" array only contains strings */
1957
                                gboolean ok = TRUE;
1958
                                if(json_array_size(allowed) > 0) {
1959
                                        size_t i = 0;
1960
                                        for(i=0; i<json_array_size(allowed); i++) {
1961
                                                json_t *a = json_array_get(allowed, i);
1962
                                                if(!a || !json_is_string(a)) {
1963
                                                        ok = FALSE;
1964
                                                        break;
1965
                                                }
1966
                                        }
1967
                                }
1968
                                if(!ok) {
1969
                                        JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
1970
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1971
                                        g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
1972
                                        janus_mutex_unlock(&rooms_mutex);
1973
                                        goto plugin_response;
1974
                                }
1975
                                size_t i = 0;
1976
                                for(i=0; i<json_array_size(allowed); i++) {
1977
                                        const char *token = json_string_value(json_array_get(allowed, i));
1978
                                        if(add) {
1979
                                                if(!g_hash_table_lookup(videoroom->allowed, token))
1980
                                                        g_hash_table_insert(videoroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
1981
                                        } else {
1982
                                                g_hash_table_remove(videoroom->allowed, token);
1983
                                        }
1984
                                }
1985
                        }
1986
                }
1987
                /* Prepare response */
1988
                response = json_object();
1989
                json_object_set_new(response, "videoroom", json_string("success"));
1990
                json_object_set_new(response, "room", json_integer(videoroom->room_id));
1991
                json_t *list = json_array();
1992
                if(strcasecmp(action_text, "disable")) {
1993
                        if(g_hash_table_size(videoroom->allowed) > 0) {
1994
                                GHashTableIter iter;
1995
                                gpointer key;
1996
                                g_hash_table_iter_init(&iter, videoroom->allowed);
1997
                                while(g_hash_table_iter_next(&iter, &key, NULL)) {
1998
                                        char *token = key;
1999
                                        json_array_append_new(list, json_string(token));
2000
                                }
2001
                        }
2002
                        json_object_set_new(response, "allowed", list);
2003
                }
2004
                /* Done */
2005
                janus_mutex_unlock(&rooms_mutex);
2006
                JANUS_LOG(LOG_VERB, "VideoRoom room allowed list updated\n");
2007
                goto plugin_response;
2008
        } else if(!strcasecmp(request_text, "kick")) {
2009
                JANUS_LOG(LOG_VERB, "Attempt to kick a participant from an existing videoroom room\n");
2010
                JANUS_VALIDATE_JSON_OBJECT(root, kick_parameters,
2011
                        error_code, error_cause, TRUE,
2012
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2013
                if(error_code != 0)
2014
                        goto plugin_response;
2015
                json_t *room = json_object_get(root, "room");
2016
                json_t *id = json_object_get(root, "id");
2017
                guint64 room_id = json_integer_value(room);
2018
                janus_mutex_lock(&rooms_mutex);
2019
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
2020
                if(videoroom == NULL) {
2021
                        janus_mutex_unlock(&rooms_mutex);
2022
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2023
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2024
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2025
                        goto plugin_response;
2026
                }
2027
                janus_mutex_lock(&videoroom->participants_mutex);
2028
                janus_mutex_unlock(&rooms_mutex);
2029
                /* A secret may be required for this action */
2030
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
2031
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2032
                if(error_code != 0) {
2033
                        janus_mutex_unlock(&videoroom->participants_mutex);
2034
                        goto plugin_response;
2035
                }
2036
                guint64 user_id = json_integer_value(id);
2037
                janus_videoroom_participant *participant = g_hash_table_lookup(videoroom->participants, &user_id);
2038
                if(participant == NULL) {
2039
                        janus_mutex_unlock(&videoroom->participants_mutex);
2040
                        JANUS_LOG(LOG_ERR, "No such user %"SCNu64" in room %"SCNu64"\n", user_id, room_id);
2041
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2042
                        g_snprintf(error_cause, 512, "No such user %"SCNu64" in room %"SCNu64, user_id, room_id);
2043
                        goto plugin_response;
2044
                }
2045
                if(participant->kicked) {
2046
                        /* Already kicked */
2047
                        janus_mutex_unlock(&videoroom->participants_mutex);
2048
                        response = json_object();
2049
                        json_object_set_new(response, "videoroom", json_string("success"));
2050
                        /* Done */
2051
                        goto plugin_response;
2052
                }
2053
                participant->kicked = TRUE;
2054
                participant->session->started = FALSE;
2055
                participant->audio_active = FALSE;
2056
                participant->video_active = FALSE;
2057
                participant->data_active = FALSE;
2058
                /* Prepare an event for this */
2059
                json_t *kicked = json_object();
2060
                json_object_set_new(kicked, "videoroom", json_string("event"));
2061
                json_object_set_new(kicked, "room", json_integer(participant->room->room_id));
2062
                json_object_set_new(kicked, "leaving", json_string("ok"));
2063
                json_object_set_new(kicked, "reason", json_string("kicked"));
2064
                int ret = gateway->push_event(participant->session->handle, &janus_videoroom_plugin, NULL, kicked, NULL);
2065
                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
2066
                json_decref(kicked);
2067
                janus_mutex_unlock(&videoroom->participants_mutex);
2068
                /* If this room requires valid private_id values, we can kick subscriptions too */
2069
                if(videoroom->require_pvtid && participant->subscriptions != NULL) {
2070
                        /* Iterate on the subscriptions we know this user has */
2071
                        janus_mutex_lock(&participant->listeners_mutex);
2072
                        GSList *s = participant->subscriptions;
2073
                        while(s) {
2074
                                janus_videoroom_listener *listener = (janus_videoroom_listener *)s->data;
2075
                                if(listener) {
2076
                                        listener->kicked = TRUE;
2077
                                        listener->audio = FALSE;
2078
                                        listener->video = FALSE;
2079
                                        listener->data = FALSE;
2080
                                        /* FIXME We should also close the PeerConnection, but we risk race conditions if we do it here,
2081
                                         * so for now we mark the listener as kicked and prevent it from getting any media after this */
2082
                                }
2083
                                s = s->next;
2084
                        }
2085
                        janus_mutex_unlock(&participant->listeners_mutex);
2086
                }
2087
                /* This publisher is leaving, tell everybody */
2088
                janus_videoroom_leave_or_unpublish(participant, TRUE, TRUE);
2089
                /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
2090
                if(participant && participant->session)
2091
                        gateway->close_pc(participant->session->handle);
2092
                JANUS_LOG(LOG_INFO, "Kicked user %"SCNu64" from room %"SCNu64"\n", user_id, room_id);
2093
                /* Prepare response */
2094
                response = json_object();
2095
                json_object_set_new(response, "videoroom", json_string("success"));
2096
                /* Done */
2097
                goto plugin_response;
2098
        } else if(!strcasecmp(request_text, "listparticipants")) {
2099
                /* List all participants in a room, specifying whether they're publishers or just attendees */        
2100
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2101
                        error_code, error_cause, TRUE,
2102
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2103
                if(error_code != 0)
2104
                        goto plugin_response;
2105
                json_t *room = json_object_get(root, "room");
2106
                guint64 room_id = json_integer_value(room);
2107
                janus_mutex_lock(&rooms_mutex);
2108
                janus_videoroom *videoroom = NULL;
2109
                error_code = janus_videoroom_access_room(root, FALSE, FALSE, &videoroom, error_cause, sizeof(error_cause));
2110
                janus_mutex_unlock(&rooms_mutex);
2111
                if(error_code != 0)
2112
                        goto plugin_response;
2113
                /* Return a list of all participants (whether they're publishing or not) */
2114
                json_t *list = json_array();
2115
                GHashTableIter iter;
2116
                gpointer value;
2117
                janus_mutex_lock(&videoroom->participants_mutex);
2118
                g_hash_table_iter_init(&iter, videoroom->participants);
2119
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2120
                        janus_videoroom_participant *p = value;
2121
                        json_t *pl = json_object();
2122
                        json_object_set_new(pl, "id", json_integer(p->user_id));
2123
                        if(p->display)
2124
                                json_object_set_new(pl, "display", json_string(p->display));
2125
                        json_object_set_new(pl, "publisher", (p->sdp && p->session->started) ? json_true() : json_false());
2126
                        if ((p->sdp && p->session->started)) {
2127
                                if(p->audio_level_extmap_id > 0)
2128
                                        json_object_set_new(pl, "talking", p->talking ? json_true() : json_false());
2129
                                json_object_set_new(pl, "internal_audio_ssrc", json_integer(p->audio_ssrc));
2130
                                json_object_set_new(pl, "internal_video_ssrc", json_integer(p->video_ssrc));
2131
                        }
2132
                        json_array_append_new(list, pl);
2133
                }
2134
                janus_mutex_unlock(&videoroom->participants_mutex);
2135
                response = json_object();
2136
                json_object_set_new(response, "videoroom", json_string("participants"));
2137
                json_object_set_new(response, "room", json_integer(room_id));
2138
                json_object_set_new(response, "participants", list);
2139
                goto plugin_response;
2140
        } else if(!strcasecmp(request_text, "listforwarders")) {
2141
                /* List all forwarders in a room */        
2142
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2143
                        error_code, error_cause, TRUE,
2144
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2145
                if(error_code != 0)
2146
                        goto plugin_response;
2147
                json_t *room = json_object_get(root, "room");
2148
                guint64 room_id = json_integer_value(room);
2149
                janus_mutex_lock(&rooms_mutex);
2150
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
2151
                if(videoroom == NULL) {
2152
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2153
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2154
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2155
                        janus_mutex_unlock(&rooms_mutex);
2156
                        goto plugin_response;
2157
                }
2158
                if(videoroom->destroyed) {
2159
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2160
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2161
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2162
                        janus_mutex_unlock(&rooms_mutex);
2163
                        goto plugin_response;
2164
                }
2165
                /* A secret may be required for this action */
2166
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
2167
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2168
                if(error_code != 0) {
2169
                        janus_mutex_unlock(&rooms_mutex);
2170
                        goto plugin_response;
2171
                }
2172
                /* Return a list of all forwarders */
2173
                json_t *list = json_array();
2174
                GHashTableIter iter;
2175
                gpointer value;
2176
                janus_mutex_lock(&videoroom->participants_mutex);
2177
                g_hash_table_iter_init(&iter, videoroom->participants);
2178
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2179
                        janus_videoroom_participant *p = value;
2180
                        janus_mutex_lock(&p->rtp_forwarders_mutex);
2181
                        if(g_hash_table_size(p->rtp_forwarders) == 0) {
2182
                                janus_mutex_unlock(&p->rtp_forwarders_mutex);
2183
                                continue;
2184
                        }
2185
                        json_t *pl = json_object();
2186
                        json_object_set_new(pl, "publisher_id", json_integer(p->user_id));
2187
                        if(p->display)
2188
                                json_object_set_new(pl, "display", json_string(p->display));
2189
                        json_t *flist = json_array();
2190
                        GHashTableIter iter_f;
2191
                        gpointer key_f, value_f;                        
2192
                        g_hash_table_iter_init(&iter_f, p->rtp_forwarders);
2193
                        while(g_hash_table_iter_next(&iter_f, &key_f, &value_f)) {                                
2194
                                json_t *fl = json_object();
2195
                                guint32 rpk = GPOINTER_TO_UINT(key_f);
2196
                                janus_videoroom_rtp_forwarder *rpv = value_f;
2197
                                json_object_set_new(fl, "ip", json_string(inet_ntoa(rpv->serv_addr.sin_addr)));
2198
                                if(rpv->is_data) {
2199
                                        json_object_set_new(fl, "data_stream_id", json_integer(rpk));
2200
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
2201
                                } else if(rpv->is_video) {
2202
                                        json_object_set_new(fl, "video_stream_id", json_integer(rpk));
2203
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
2204
                                        if(rpv->payload_type)
2205
                                                json_object_set_new(fl, "pt", json_integer(rpv->payload_type));
2206
                                        if(rpv->ssrc)
2207
                                                json_object_set_new(fl, "ssrc", json_integer(rpv->ssrc));
2208
                                } else {
2209
                                        json_object_set_new(fl, "audio_stream_id", json_integer(rpk));
2210
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
2211
                                        if(rpv->payload_type)
2212
                                                json_object_set_new(fl, "pt", json_integer(rpv->payload_type));
2213
                                        if(rpv->ssrc)
2214
                                                json_object_set_new(fl, "ssrc", json_integer(rpv->ssrc));
2215
                                }
2216
                                json_array_append_new(flist, fl);
2217
                        }                
2218
                        janus_mutex_unlock(&p->rtp_forwarders_mutex);
2219
                        json_object_set_new(pl, "rtp_forwarder", flist);
2220
                        json_array_append_new(list, pl);
2221
                }
2222
                janus_mutex_unlock(&videoroom->participants_mutex);
2223
                janus_mutex_unlock(&rooms_mutex);
2224
                response = json_object();
2225
                json_object_set_new(response, "room", json_integer(room_id));
2226
                json_object_set_new(response, "rtp_forwarders", list);
2227
                goto plugin_response;
2228
        } else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")
2229
                        || !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish") || !strcasecmp(request_text, "unpublish")
2230
                        || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "switch") || !strcasecmp(request_text, "stop")
2231
                        || !strcasecmp(request_text, "add") || !strcasecmp(request_text, "remove") || !strcasecmp(request_text, "leave")) {
2232
                /* These messages are handled asynchronously */
2233

    
2234
                janus_videoroom_message *msg = g_malloc0(sizeof(janus_videoroom_message));
2235
                msg->handle = handle;
2236
                msg->transaction = transaction;
2237
                msg->message = root;
2238
                msg->jsep = jsep;
2239
                g_async_queue_push(messages, msg);
2240

    
2241
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
2242
        } else {
2243
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
2244
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
2245
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
2246
        }
2247

    
2248
plugin_response:
2249
                {
2250
                        if(error_code == 0 && !response) {
2251
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2252
                                g_snprintf(error_cause, 512, "Invalid response");
2253
                        }
2254
                        if(error_code != 0) {
2255
                                /* Prepare JSON error event */
2256
                                json_t *event = json_object();
2257
                                json_object_set_new(event, "videoroom", json_string("event"));
2258
                                json_object_set_new(event, "error_code", json_integer(error_code));
2259
                                json_object_set_new(event, "error", json_string(error_cause));
2260
                                response = event;
2261
                        }
2262
                        if(root != NULL)
2263
                                json_decref(root);
2264
                        if(jsep != NULL)
2265
                                json_decref(jsep);
2266
                        g_free(transaction);
2267

    
2268
                        return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
2269
                }
2270

    
2271
}
2272

    
2273
void janus_videoroom_setup_media(janus_plugin_session *handle) {
2274
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
2275
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2276
                return;
2277
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2278
        if(!session) {
2279
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2280
                return;
2281
        }
2282
        if(session->destroyed)
2283
                return;
2284
        g_atomic_int_set(&session->hangingup, 0);
2285

    
2286
        /* Media relaying can start now */
2287
        session->started = TRUE;
2288

    
2289
        if(session->participant) {
2290
                /* If this is a publisher, notify all listeners about the fact they can
2291
                 * now subscribe; if this is a listener, instead, ask the publisher a FIR */
2292
                if(session->participant_type == janus_videoroom_p_type_publisher) {
2293
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2294
                        /* Notify all other participants that there's a new boy in town */
2295
                        json_t *list = json_array();
2296
                        json_t *pl = json_object();
2297
                        json_object_set_new(pl, "id", json_integer(participant->user_id));
2298
                        if(participant->display)
2299
                                json_object_set_new(pl, "display", json_string(participant->display));
2300
                        json_array_append_new(list, pl);
2301
                        json_t *pub = json_object();
2302
                        json_object_set_new(pub, "videoroom", json_string("event"));
2303
                        json_object_set_new(pub, "room", json_integer(participant->room->room_id));
2304
                        json_object_set_new(pub, "publishers", list);
2305
                        GHashTableIter iter;
2306
                        gpointer value;
2307
                        janus_videoroom *videoroom = participant->room;
2308
                        janus_mutex_lock(&videoroom->participants_mutex);
2309
                        g_hash_table_iter_init(&iter, videoroom->participants);
2310
                        while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2311
                                janus_videoroom_participant *p = value;
2312
                                if(p == participant) {
2313
                                        continue;        /* Skip the new publisher itself */
2314
                                }
2315
                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2316
                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub, NULL);
2317
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
2318
                        }
2319
                        json_decref(pub);
2320
                        janus_mutex_unlock(&videoroom->participants_mutex);
2321
                        /* Also notify event handlers */
2322
                        if(notify_events && gateway->events_is_enabled()) {
2323
                                json_t *info = json_object();
2324
                                json_object_set_new(info, "event", json_string("published"));
2325
                                json_object_set_new(info, "room", json_integer(participant->room->room_id));
2326
                                json_object_set_new(info, "id", json_integer(participant->user_id));
2327
                                gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2328
                        }
2329
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2330
                        janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
2331
                        if(l && l->feed) {
2332
                                janus_videoroom_participant *p = l->feed;
2333
                                if(p && p->session) {
2334
                                        /* Send a FIR */
2335
                                        char buf[20];
2336
                                        janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
2337
                                        JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2338
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 20);
2339
                                        /* Send a PLI too, just in case... */
2340
                                        janus_rtcp_pli((char *)&buf, 12);
2341
                                        JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2342
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 12);
2343
                                        /* Also notify event handlers */
2344
                                        if(notify_events && gateway->events_is_enabled()) {
2345
                                                json_t *info = json_object();
2346
                                                json_object_set_new(info, "event", json_string("subscribed"));
2347
                                                json_object_set_new(info, "room", json_integer(p->room->room_id));
2348
                                                json_object_set_new(info, "feed", json_integer(p->user_id));
2349
                                                gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2350
                                        }
2351
                                }
2352
                        }
2353
                }
2354
        }
2355
}
2356

    
2357
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
2358
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2359
                return;
2360
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2361
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
2362
                return;
2363
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2364
        janus_videoroom *videoroom = participant->room;
2365

    
2366
        if(participant->kicked)
2367
                return;
2368
        /* In case this is an audio packet and we're doing talk detection, check the audio level extension */
2369
        if(!video && videoroom->audiolevel_event && participant->audio_active) {
2370
                int level = 0;
2371
                if(janus_rtp_header_extension_parse_audio_level(buf, len, participant->audio_level_extmap_id, &level) == 0) {
2372
                        participant->audio_dBov_sum += level;
2373
                        participant->audio_active_packets++;
2374
                        participant->audio_dBov_level = level;
2375
                        if(participant->audio_active_packets > 0 && participant->audio_active_packets == videoroom->audio_active_packets) {
2376
                                gboolean notify_talk_event = FALSE;
2377
                                if((float)participant->audio_dBov_sum/(float)participant->audio_active_packets < videoroom->audio_level_average) {
2378
                                        /* Participant talking, should we notify all participants? */
2379
                                        if(!participant->talking)
2380
                                                notify_talk_event = TRUE;
2381
                                        participant->talking = TRUE;
2382
                                } else {
2383
                                        /* Participant not talking anymore, should we notify all participants? */
2384
                                        if(participant->talking)
2385
                                                notify_talk_event = TRUE;
2386
                                        participant->talking = FALSE;
2387
                                }
2388
                                participant->audio_active_packets = 0;
2389
                                participant->audio_dBov_sum = 0;
2390
                                /* Only notify in case of state changes */
2391
                                if(notify_talk_event) {
2392
                                        janus_mutex_lock(&participant->room->participants_mutex);
2393
                                        json_t *event = json_object();
2394
                                        json_object_set_new(event, "videoroom", json_string(participant->talking ? "talking" : "stopped-talking"));
2395
                                        json_object_set_new(event, "room", json_integer(participant->room->room_id));
2396
                                        json_object_set_new(event, "id", json_integer(participant->user_id));
2397
                                        janus_videoroom_notify_participants(participant, event);
2398
                                        json_decref(event);
2399
                                        janus_mutex_unlock(&participant->room->participants_mutex);
2400
                                        /* Also notify event handlers */
2401
                                        if(notify_events && gateway->events_is_enabled()) {
2402
                                                json_t *info = json_object();
2403
                                                json_object_set_new(info, "videoroom", json_string(participant->talking ? "talking" : "stopped-talking"));
2404
                                                json_object_set_new(info, "room", json_integer(participant->room->room_id));
2405
                                                json_object_set_new(info, "id", json_integer(participant->user_id));
2406
                                                gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2407
                                        }
2408
                                }
2409
                        }
2410
                }
2411
        }
2412

    
2413
        if((!video && participant->audio_active) || (video && participant->video_active)) {
2414
                /* Update payload type and SSRC */
2415
                janus_mutex_lock(&participant->rtp_forwarders_mutex);
2416
                rtp_header *rtp = (rtp_header *)buf;
2417
                rtp->type = video ? participant->video_pt : participant->audio_pt;
2418
                rtp->ssrc = htonl(video ? participant->video_ssrc : participant->audio_ssrc);
2419
                /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
2420
                GHashTableIter iter;
2421
                gpointer value;
2422
                g_hash_table_iter_init(&iter, participant->rtp_forwarders);
2423
                while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
2424
                        janus_videoroom_rtp_forwarder* rtp_forward = (janus_videoroom_rtp_forwarder*)value;
2425
                        /* Check if payload type and/or SSRC need to be overwritten for this forwarder */
2426
                        int pt = rtp->type;
2427
                        uint32_t ssrc = ntohl(rtp->ssrc);
2428
                        if(rtp_forward->payload_type > 0)
2429
                                rtp->type = rtp_forward->payload_type;
2430
                        if(rtp_forward->ssrc > 0)
2431
                                rtp->ssrc = htonl(rtp_forward->ssrc);
2432
                        if(video && rtp_forward->is_video) {
2433
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
2434
                        }
2435
                        else if(!video && !rtp_forward->is_video && !rtp_forward->is_data) {
2436
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
2437
                        }
2438
                        /* Restore original values of payload type and SSRC before going on */
2439
                        rtp->type = pt;
2440
                        rtp->ssrc = htonl(ssrc);
2441
                }
2442
                janus_mutex_unlock(&participant->rtp_forwarders_mutex);
2443
                /* Save the frame if we're recording */
2444
                janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len);
2445
                /* Done, relay it */
2446
                janus_videoroom_rtp_relay_packet packet;
2447
                packet.data = rtp;
2448
                packet.length = len;
2449
                packet.is_video = video;
2450
                packet.svc = FALSE;
2451
                if(video && videoroom->do_svc) {
2452
                        /* We're doing SVC: let's parse this packet to see which layers are there */
2453
                        uint8_t pbit = 0, dbit = 0, ubit = 0, bbit = 0, ebit = 0;
2454
                        int found = 0, spatial_layer = 0, temporal_layer = 0;
2455
                        if(janus_videoroom_videocodec_parse_svc(videoroom->vcodec, buf, len, &found, &spatial_layer, &temporal_layer, &pbit, &dbit, &ubit, &bbit, &ebit) == 0) {
2456
                                if(found) {
2457
                                        packet.svc = TRUE;
2458
                                        packet.spatial_layer = spatial_layer;
2459
                                        packet.temporal_layer = temporal_layer;
2460
                                        packet.pbit = pbit;
2461
                                        packet.dbit = dbit;
2462
                                        packet.ubit = ubit;
2463
                                        packet.bbit = bbit;
2464
                                        packet.ebit = ebit;
2465
                                        JANUS_LOG(LOG_WARN, "sl=%d, tl=%d, p=%u, d=%u, u=%u, b=%u, e=%u\n",
2466
                                                packet.spatial_layer, packet.temporal_layer,
2467
                                                packet.pbit, packet.dbit, packet.ubit, packet.bbit, packet.ebit);
2468
                                }
2469
                        }
2470
                }
2471
                /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */
2472
                packet.timestamp = ntohl(packet.data->timestamp);
2473
                packet.seq_number = ntohs(packet.data->seq_number);
2474
                /* Go */
2475
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
2476
                
2477
                /* Check if we need to send any REMB, FIR or PLI back to this publisher */
2478
                if(video && participant->video_active) {
2479
                        /* Did we send a REMB already, or is it time to send one? */
2480
                        gboolean send_remb = FALSE;
2481
                        if(participant->remb_latest == 0 && participant->remb_startup > 0) {
2482
                                /* Still in the starting phase, send the ramp-up REMB feedback */
2483
                                send_remb = TRUE;
2484
                        } else if(participant->remb_latest > 0 && janus_get_monotonic_time()-participant->remb_latest >= 5*G_USEC_PER_SEC) {
2485
                                /* 5 seconds have passed since the last REMB, send a new one */
2486
                                send_remb = TRUE;
2487
                        }                
2488
                        if(send_remb) {
2489
                                /* We send a few incremental REMB messages at startup */
2490
                                uint64_t bitrate = (participant->bitrate ? participant->bitrate : 256*1024);
2491
                                if(participant->remb_startup > 0) {
2492
                                        bitrate = bitrate/participant->remb_startup;
2493
                                        participant->remb_startup--;
2494
                                }
2495
                                JANUS_LOG(LOG_VERB, "Sending REMB (%s, %"SCNu64")\n", participant->display, bitrate);
2496
                                char rtcpbuf[24];
2497
                                janus_rtcp_remb((char *)(&rtcpbuf), 24, bitrate);
2498
                                gateway->relay_rtcp(handle, video, rtcpbuf, 24);
2499
                                if(participant->remb_startup == 0)
2500
                                        participant->remb_latest = janus_get_monotonic_time();
2501
                        }
2502
                        /* Generate FIR/PLI too, if needed */
2503
                        if(video && participant->video_active && (participant->room->fir_freq > 0)) {
2504
                                /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
2505
                                gint64 now = janus_get_monotonic_time();
2506
                                if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
2507
                                        /* FIXME We send a FIR every tot seconds */
2508
                                        participant->fir_latest = now;
2509
                                        char rtcpbuf[24];
2510
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &participant->fir_seq);
2511
                                        JANUS_LOG(LOG_VERB, "Sending FIR to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
2512
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 20);
2513
                                        /* Send a PLI too, just in case... */
2514
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2515
                                        JANUS_LOG(LOG_VERB, "Sending PLI to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
2516
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 12);
2517
                                }
2518
                        }
2519
                }
2520
        }
2521
}
2522

    
2523
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
2524
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2525
                return;
2526
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2527
        if(!session) {
2528
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2529
                return;
2530
        }
2531
        if(session->destroyed)
2532
                return;
2533
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
2534
                /* A listener sent some RTCP, check what it is and if we need to forward it to the publisher */
2535
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
2536
                if(!l || !l->video)
2537
                        return;        /* The only feedback we handle is video related anyway... */
2538
                if(janus_rtcp_has_fir(buf, len)) {
2539
                        /* We got a FIR, forward it to the publisher */
2540
                        if(l->feed) {
2541
                                janus_videoroom_participant *p = l->feed;
2542
                                if(p && p->session) {
2543
                                        char rtcpbuf[20];
2544
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &p->fir_seq);
2545
                                        JANUS_LOG(LOG_VERB, "Got a FIR from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2546
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 20);
2547
                                }
2548
                        }
2549
                }
2550
                if(janus_rtcp_has_pli(buf, len)) {
2551
                        /* We got a PLI, forward it to the publisher */
2552
                        if(l->feed) {
2553
                                janus_videoroom_participant *p = l->feed;
2554
                                if(p && p->session) {
2555
                                        char rtcpbuf[12];
2556
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2557
                                        JANUS_LOG(LOG_VERB, "Got a PLI from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2558
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 12);
2559
                                }
2560
                        }
2561
                }
2562
                uint64_t bitrate = janus_rtcp_get_remb(buf, len);
2563
                if(bitrate > 0) {
2564
                        /* FIXME We got a REMB from this listener, should we do something about it? */
2565
                }
2566
        }
2567
}
2568

    
2569
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
2570
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2571
                return;
2572
        if(buf == NULL || len <= 0)
2573
                return;
2574
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2575
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
2576
                return;
2577
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2578
        if(!participant->data_active || participant->kicked)
2579
                return;
2580
        /* Any forwarder involved? */
2581
        janus_mutex_lock(&participant->rtp_forwarders_mutex);
2582
        /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
2583
        GHashTableIter iter;
2584
        gpointer value;
2585
        g_hash_table_iter_init(&iter, participant->rtp_forwarders);
2586
        while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
2587
                janus_videoroom_rtp_forwarder* rtp_forward = (janus_videoroom_rtp_forwarder*)value;
2588
                if(rtp_forward->is_data) {
2589
                        sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
2590
                }
2591
        }
2592
        janus_mutex_unlock(&participant->rtp_forwarders_mutex);
2593
        /* Get a string out of the data */
2594
        char *text = g_malloc0(len+1);
2595
        memcpy(text, buf, len);
2596
        *(text+len) = '\0';
2597
        JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
2598
        /* Save the message if we're recording */
2599
        janus_recorder_save_frame(participant->drc, text, strlen(text));
2600
        /* Relay to all listeners */
2601
        g_slist_foreach(participant->listeners, janus_videoroom_relay_data_packet, text);
2602
        g_free(text);
2603
}
2604

    
2605
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
2606
        /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */
2607
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2608
                return;
2609
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2610
        if(!session || session->destroyed || !session->participant)
2611
                return;
2612
        /* Check if it's an uplink (publisher) or downlink (viewer) issue */
2613
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2614
                if(!uplink) {
2615
                        janus_videoroom_participant *publisher = (janus_videoroom_participant *)session->participant;
2616
                        if(publisher) {
2617
                                /* Send an event on the handle to notify the application: it's
2618
                                 * up to the application to then choose a policy and enforce it */
2619
                                json_t *event = json_object();
2620
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2621
                                /* Also add info on what the current bitrate cap is */
2622
                                uint64_t bitrate = (publisher->bitrate ? publisher->bitrate : 256*1024);
2623
                                json_object_set_new(event, "current-bitrate", json_integer(bitrate));
2624
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
2625
                                json_decref(event);
2626
                        }
2627
                } else {
2628
                        JANUS_LOG(LOG_WARN, "Got a slow uplink on a VideoRoom publisher? Weird, because it doesn't receive media...\n");
2629
                }
2630
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2631
                if(uplink) {
2632
                        janus_videoroom_listener *viewer = (janus_videoroom_listener *)session->participant;
2633
                        if(viewer) {
2634
                                /* Send an event on the handle to notify the application: it's
2635
                                 * up to the application to then choose a policy and enforce it */
2636
                                json_t *event = json_object();
2637
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2638
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
2639
                                json_decref(event);
2640
                        }
2641
                } else {
2642
                        JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
2643
                }
2644
        }
2645
}
2646

    
2647
static void janus_videoroom_recorder_create(janus_videoroom_participant *participant, gboolean audio, gboolean video, gboolean data) {
2648
        char filename[255];
2649
        gint64 now = janus_get_real_time();
2650
        if(audio) {
2651
                memset(filename, 0, 255);
2652
                if(participant->recording_base) {
2653
                        /* Use the filename and path we have been provided */
2654
                        g_snprintf(filename, 255, "%s-audio", participant->recording_base);
2655
                        participant->arc = janus_recorder_create(participant->room->rec_dir,
2656
                                janus_videoroom_audiocodec_name(participant->room->acodec), filename);
2657
                        if(participant->arc == NULL) {
2658
                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2659
                        }
2660
                } else {
2661
                        /* Build a filename */
2662
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
2663
                                participant->room->room_id, participant->user_id, now);
2664
                        participant->arc = janus_recorder_create(participant->room->rec_dir,
2665
                                janus_videoroom_audiocodec_name(participant->room->acodec), filename);
2666
                        if(participant->arc == NULL) {
2667
                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2668
                        }
2669
                }
2670
        }
2671
        if(video) {
2672
                memset(filename, 0, 255);
2673
                if(participant->recording_base) {
2674
                        /* Use the filename and path we have been provided */
2675
                        g_snprintf(filename, 255, "%s-video", participant->recording_base);
2676
                        participant->vrc = janus_recorder_create(participant->room->rec_dir,
2677
                                janus_videoroom_videocodec_name(participant->room->vcodec), filename);
2678
                        if(participant->vrc == NULL) {
2679
                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2680
                        }
2681
                } else {
2682
                        /* Build a filename */
2683
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
2684
                                participant->room->room_id, participant->user_id, now);
2685
                        participant->vrc = janus_recorder_create(participant->room->rec_dir,
2686
                                janus_videoroom_videocodec_name(participant->room->vcodec), filename);
2687
                        if(participant->vrc == NULL) {
2688
                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2689
                        }
2690
                }
2691
        }
2692
        if(data) {
2693
                memset(filename, 0, 255);
2694
                if(participant->recording_base) {
2695
                        /* Use the filename and path we have been provided */
2696
                        g_snprintf(filename, 255, "%s-data", participant->recording_base);
2697
                        participant->drc = janus_recorder_create(participant->room->rec_dir,
2698
                                "text", filename);
2699
                        if(participant->drc == NULL) {
2700
                                JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
2701
                        }
2702
                } else {
2703
                        /* Build a filename */
2704
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-data",
2705
                                participant->room->room_id, participant->user_id, now);
2706
                        participant->drc = janus_recorder_create(participant->room->rec_dir,
2707
                                "text", filename);
2708
                        if(participant->drc == NULL) {
2709
                                JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
2710
                        }
2711
                }
2712
        }
2713
}
2714

    
2715
static void janus_videoroom_recorder_close(janus_videoroom_participant *participant) {
2716
        if(participant->arc) {
2717
                janus_recorder_close(participant->arc);
2718
                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
2719
                janus_recorder_free(participant->arc);
2720
        }
2721
        participant->arc = NULL;
2722
        if(participant->vrc) {
2723
                janus_recorder_close(participant->vrc);
2724
                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
2725
                janus_recorder_free(participant->vrc);
2726
        }
2727
        participant->vrc = NULL;
2728
        if(participant->drc) {
2729
                janus_recorder_close(participant->drc);
2730
                JANUS_LOG(LOG_INFO, "Closed data recording %s\n", participant->drc->filename ? participant->drc->filename : "??");
2731
                janus_recorder_free(participant->drc);
2732
        }
2733
        participant->drc = NULL;
2734
}
2735

    
2736
void janus_videoroom_hangup_media(janus_plugin_session *handle) {
2737
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
2738
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2739
                return;
2740
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2741
        if(!session) {
2742
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2743
                return;
2744
        }
2745
        session->started = FALSE;
2746
        if(session->destroyed)
2747
                return;
2748
        if(g_atomic_int_add(&session->hangingup, 1))
2749
                return;
2750
        /* Send an event to the browser and tell the PeerConnection is over */
2751
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2752
                /* This publisher just 'unpublished' */
2753
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2754
                if(participant->sdp)
2755
                        g_free(participant->sdp);
2756
                participant->sdp = NULL;
2757
                participant->firefox = FALSE;
2758
                participant->audio_active = FALSE;
2759
                participant->video_active = FALSE;
2760
                participant->data_active = FALSE;
2761
                participant->audio_active_packets = 0;
2762
                participant->audio_dBov_sum = 0;
2763
                participant->audio_dBov_level = 0;
2764
                participant->talking = FALSE;
2765
                participant->remb_startup = 4;
2766
                participant->remb_latest = 0;
2767
                participant->fir_latest = 0;
2768
                participant->fir_seq = 0;
2769
                /* Get rid of the recorders, if available */
2770
                janus_mutex_lock(&participant->rec_mutex);
2771
                janus_videoroom_recorder_close(participant);
2772
                janus_mutex_unlock(&participant->rec_mutex);
2773
                janus_mutex_lock(&participant->listeners_mutex);
2774
                while(participant->listeners) {
2775
                        janus_videoroom_listener *l = (janus_videoroom_listener *)participant->listeners->data;
2776
                        if(l) {
2777
                                participant->listeners = g_slist_remove(participant->listeners, l);
2778
                                l->feed = NULL;
2779
                        }
2780
                }
2781
                janus_mutex_unlock(&participant->listeners_mutex);
2782
                janus_videoroom_leave_or_unpublish(participant, FALSE, FALSE);
2783
                /* Also notify event handlers */
2784
                if(participant->room && gateway->events_is_enabled()) {
2785
                        json_t *info = json_object();
2786
                        json_object_set_new(info, "event", json_string("unpublished"));
2787
                        json_object_set_new(info, "room", json_integer(participant->room->room_id));
2788
                        json_object_set_new(info, "id", json_integer(participant->user_id));
2789
                        gateway->notify_event(&janus_videoroom_plugin, handle, info);
2790
                }
2791
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2792
                /* Get rid of listener */
2793
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2794
                if(listener) {
2795
                        listener->paused = TRUE;
2796
                        janus_videoroom_participant *publisher = listener->feed;
2797
                        if(publisher != NULL) {
2798
                                janus_mutex_lock(&publisher->listeners_mutex);
2799
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
2800
                                janus_mutex_unlock(&publisher->listeners_mutex);
2801
                                listener->feed = NULL;
2802
                                if(listener->pvt_id > 0) {
2803
                                        janus_videoroom_participant *owner = g_hash_table_lookup(publisher->room->private_ids, GUINT_TO_POINTER(listener->pvt_id));
2804
                                        if(owner != NULL) {
2805
                                                janus_mutex_lock(&owner->listeners_mutex);
2806
                                                owner->subscriptions = g_slist_remove(owner->subscriptions, listener);
2807
                                                janus_mutex_unlock(&owner->listeners_mutex);
2808
                                        }
2809
                                }
2810
                                /* Also notify event handlers */
2811
                                if(notify_events && gateway->events_is_enabled()) {
2812
                                        json_t *info = json_object();
2813
                                        json_object_set_new(info, "event", json_string("unsubscribed"));
2814
                                        json_object_set_new(info, "room", json_integer(publisher->room->room_id));
2815
                                        json_object_set_new(info, "feed", json_integer(publisher->user_id));
2816
                                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2817
                                }
2818
                        }
2819
                }
2820
                /* TODO Should we close the handle as well? */
2821
        }
2822
}
2823

    
2824
/* Thread to handle incoming messages */
2825
static void *janus_videoroom_handler(void *data) {
2826
        JANUS_LOG(LOG_VERB, "Joining VideoRoom handler thread\n");
2827
        janus_videoroom_message *msg = NULL;
2828
        int error_code = 0;
2829
        char error_cause[512];
2830
        json_t *root = NULL;
2831
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
2832
                msg = g_async_queue_pop(messages);
2833
                if(msg == NULL)
2834
                        continue;
2835
                if(msg == &exit_message)
2836
                        break;
2837
                if(msg->handle == NULL) {
2838
                        janus_videoroom_message_free(msg);
2839
                        continue;
2840
                }
2841
                janus_videoroom_session *session = NULL;
2842
                janus_mutex_lock(&sessions_mutex);
2843
                if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
2844
                        session = (janus_videoroom_session *)msg->handle->plugin_handle;
2845
                }
2846
                janus_mutex_unlock(&sessions_mutex);
2847
                if(!session) {
2848
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2849
                        janus_videoroom_message_free(msg);
2850
                        continue;
2851
                }
2852
                if(session->destroyed) {
2853
                        janus_videoroom_message_free(msg);
2854
                        continue;
2855
                }
2856
                /* Handle request */
2857
                error_code = 0;
2858
                root = NULL;
2859
                if(msg->message == NULL) {
2860
                        JANUS_LOG(LOG_ERR, "No message??\n");
2861
                        error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
2862
                        g_snprintf(error_cause, 512, "%s", "No message??");
2863
                        goto error;
2864
                }
2865
                root = msg->message;
2866
                /* Get the request first */
2867
                JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
2868
                        error_code, error_cause, TRUE,
2869
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2870
                if(error_code != 0)
2871
                        goto error;
2872
                json_t *request = json_object_get(root, "request");
2873
                const char *request_text = json_string_value(request);
2874
                json_t *event = NULL;
2875
                /* 'create' and 'destroy' are handled synchronously: what kind of participant is this session referring to? */
2876
                if(session->participant_type == janus_videoroom_p_type_none) {
2877
                        JANUS_LOG(LOG_VERB, "Configuring new participant\n");
2878
                        /* Not configured yet, we need to do this now */
2879
                        if(strcasecmp(request_text, "join") && strcasecmp(request_text, "joinandconfigure")) {
2880
                                JANUS_LOG(LOG_ERR, "Invalid request on unconfigured participant\n");
2881
                                error_code = JANUS_VIDEOROOM_ERROR_JOIN_FIRST;
2882
                                g_snprintf(error_cause, 512, "Invalid request on unconfigured participant");
2883
                                goto error;
2884
                        }
2885
                        JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
2886
                                error_code, error_cause, TRUE,
2887
                                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2888
                        if(error_code != 0)
2889
                                goto error;
2890
                        janus_mutex_lock(&rooms_mutex);
2891
                        janus_videoroom *videoroom = NULL;
2892
                        error_code = janus_videoroom_access_room(root, FALSE, TRUE, &videoroom, error_cause, sizeof(error_cause));
2893
                        janus_mutex_unlock(&rooms_mutex);
2894
                        if(error_code != 0)
2895
                                goto error;
2896
                        json_t *ptype = json_object_get(root, "ptype");
2897
                        const char *ptype_text = json_string_value(ptype);
2898
                        if(!strcasecmp(ptype_text, "publisher")) {
2899
                                JANUS_LOG(LOG_VERB, "Configuring new publisher\n");
2900
                                JANUS_VALIDATE_JSON_OBJECT(root, publisher_parameters,
2901
                                        error_code, error_cause, TRUE,
2902
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2903
                                if(error_code != 0)
2904
                                        goto error;
2905
                                /* A token might be required to join */
2906
                                if(videoroom->check_tokens) {
2907
                                        json_t *token = json_object_get(root, "token");
2908
                                        const char *token_text = token ? json_string_value(token) : NULL;
2909
                                        if(token_text == NULL || g_hash_table_lookup(videoroom->allowed, token_text) == NULL) {
2910
                                                JANUS_LOG(LOG_ERR, "Unauthorized (not in the allowed list)\n");
2911
                                                error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
2912
                                                g_snprintf(error_cause, 512, "Unauthorized (not in the allowed list)");
2913
                                                goto error;
2914
                                        }
2915
                                }
2916
                                json_t *display = json_object_get(root, "display");
2917
                                const char *display_text = display ? json_string_value(display) : NULL;
2918
                                guint64 user_id = 0;
2919
                                json_t *id = json_object_get(root, "id");
2920
                                janus_mutex_lock(&videoroom->participants_mutex);
2921
                                if(id) {
2922
                                        user_id = json_integer_value(id);
2923
                                        if(g_hash_table_lookup(videoroom->participants, &user_id) != NULL) {
2924
                                                janus_mutex_unlock(&videoroom->participants_mutex);
2925
                                                /* User ID already taken */
2926
                                                JANUS_LOG(LOG_ERR, "User ID %"SCNu64" already exists\n", user_id);
2927
                                                error_code = JANUS_VIDEOROOM_ERROR_ID_EXISTS;
2928
                                                g_snprintf(error_cause, 512, "User ID %"SCNu64" already exists", user_id);
2929
                                                goto error;
2930
                                        }
2931
                                }
2932
                                if(user_id == 0) {
2933
                                        /* Generate a random ID */
2934
                                        while(user_id == 0) {
2935
                                                user_id = janus_random_uint64();
2936
                                                if(g_hash_table_lookup(videoroom->participants, &user_id) != NULL) {
2937
                                                        /* User ID already taken, try another one */
2938
                                                        user_id = 0;
2939
                                                }
2940
                                        }
2941
                                }
2942
                                JANUS_LOG(LOG_VERB, "  -- Publisher ID: %"SCNu64"\n", user_id);
2943
                                /* Process the request */
2944
                                json_t *audio = NULL, *video = NULL, *data = NULL,
2945
                                        *bitrate = NULL, *record = NULL, *recfile = NULL;
2946
                                if(!strcasecmp(request_text, "joinandconfigure")) {
2947
                                        /* Also configure (or publish a new feed) audio/video/bitrate for this new publisher */
2948
                                        /* join_parameters were validated earlier. */
2949
                                        audio = json_object_get(root, "audio");
2950
                                        video = json_object_get(root, "video");
2951
                                        data = json_object_get(root, "data");
2952
                                        bitrate = json_object_get(root, "bitrate");
2953
                                        record = json_object_get(root, "record");
2954
                                        recfile = json_object_get(root, "filename");
2955
                                }
2956
                                janus_videoroom_participant *publisher = g_malloc0(sizeof(janus_videoroom_participant));
2957
                                publisher->session = session;
2958
                                publisher->room = videoroom;
2959
                                publisher->user_id = user_id;
2960
                                publisher->display = display_text ? g_strdup(display_text) : NULL;
2961
                                publisher->sdp = NULL;                /* We'll deal with this later */
2962
                                publisher->audio = FALSE;        /* We'll deal with this later */
2963
                                publisher->video = FALSE;        /* We'll deal with this later */
2964
                                publisher->data = FALSE;        /* We'll deal with this later */
2965
                                publisher->audio_active = FALSE;
2966
                                publisher->video_active = FALSE;
2967
                                publisher->data_active = FALSE;
2968
                                publisher->recording_active = FALSE;
2969
                                publisher->recording_base = NULL;
2970
                                publisher->arc = NULL;
2971
                                publisher->vrc = NULL;
2972
                                publisher->drc = NULL;
2973
                                janus_mutex_init(&publisher->rec_mutex);
2974
                                publisher->firefox = FALSE;
2975
                                publisher->bitrate = videoroom->bitrate;
2976
                                publisher->listeners = NULL;
2977
                                publisher->subscriptions = NULL;
2978
                                janus_mutex_init(&publisher->listeners_mutex);
2979
                                publisher->audio_pt = OPUS_PT;
2980
                                switch(videoroom->acodec) {
2981
                                        case JANUS_VIDEOROOM_OPUS:
2982
                                                publisher->audio_pt = OPUS_PT;
2983
                                                break;
2984
                                        case JANUS_VIDEOROOM_ISAC_32K:
2985
                                                publisher->audio_pt = ISAC32_PT;
2986
                                                break;
2987
                                        case JANUS_VIDEOROOM_ISAC_16K:
2988
                                                publisher->audio_pt = ISAC16_PT;
2989
                                                break;
2990
                                        case JANUS_VIDEOROOM_PCMU:
2991
                                                publisher->audio_pt = PCMU_PT;
2992
                                                break;
2993
                                        case JANUS_VIDEOROOM_PCMA:
2994
                                                publisher->audio_pt = PCMA_PT;
2995
                                                break;
2996
                                        case JANUS_VIDEOROOM_G722:
2997
                                                publisher->audio_pt = G722_PT;
2998
                                                break;
2999
                                        default:
3000
                                                /* Shouldn't happen */
3001
                                                publisher->audio_pt = OPUS_PT;
3002
                                                break;
3003
                                }
3004
                                switch(videoroom->vcodec) {
3005
                                        case JANUS_VIDEOROOM_VP8:
3006
                                                publisher->video_pt = VP8_PT;
3007
                                                break;
3008
                                        case JANUS_VIDEOROOM_VP9:
3009
                                                publisher->video_pt = VP9_PT;
3010
                                                break;
3011
                                        case JANUS_VIDEOROOM_H264:
3012
                                                publisher->video_pt = H264_PT;
3013
                                                break;
3014
                                        default:
3015
                                                /* Shouldn't happen */
3016
                                                publisher->video_pt = VP8_PT;
3017
                                                break;
3018
                                }
3019
                                publisher->audio_ssrc = janus_random_uint32();
3020
                                publisher->video_ssrc = janus_random_uint32();
3021
                                publisher->audio_level_extmap_id = 0;
3022
                                publisher->video_orient_extmap_id = 0;
3023
                                publisher->playout_delay_extmap_id = 0;
3024
                                publisher->remb_startup = 4;
3025
                                publisher->remb_latest = 0;
3026
                                publisher->fir_latest = 0;
3027
                                publisher->fir_seq = 0;
3028
                                janus_mutex_init(&publisher->rtp_forwarders_mutex);
3029
                                publisher->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_videoroom_rtp_forwarder_free_helper);
3030
                                publisher->udp_sock = -1;
3031
                                /* Finally, generate a private ID: this is only needed in case the participant
3032
                                 * wants to allow the plugin to know which subscriptions belong to them */
3033
                                publisher->pvt_id = 0;
3034
                                while(publisher->pvt_id == 0) {
3035
                                        publisher->pvt_id = janus_random_uint32();
3036
                                        if(g_hash_table_lookup(videoroom->private_ids, GUINT_TO_POINTER(publisher->pvt_id)) != NULL) {
3037
                                                /* Private ID already taken, try another one */
3038
                                                publisher->pvt_id = 0;
3039
                                        }
3040
                                        g_hash_table_insert(videoroom->private_ids, GUINT_TO_POINTER(publisher->pvt_id), publisher);
3041
                                }
3042
                                /* In case we also wanted to configure */
3043
                                if(audio) {
3044
                                        publisher->audio_active = json_is_true(audio);
3045
                                        JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->audio_active ? "true" : "false", publisher->room->room_id, publisher->user_id);
3046
                                }
3047
                                if(video) {
3048
                                        publisher->video_active = json_is_true(video);
3049
                                        JANUS_LOG(LOG_VERB, "Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->video_active ? "true" : "false", publisher->room->room_id, publisher->user_id);
3050
                                }
3051
                                if(data) {
3052
                                        publisher->data_active = json_is_true(data);
3053
                                        JANUS_LOG(LOG_VERB, "Setting data property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->data_active ? "true" : "false", publisher->room->room_id, publisher->user_id);
3054
                                }
3055
                                if(bitrate) {
3056
                                        publisher->bitrate = json_integer_value(bitrate);
3057
                                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", publisher->bitrate, publisher->room->room_id, publisher->user_id);
3058
                                }
3059
                                if(record) {
3060
                                        publisher->recording_active = json_is_true(record);
3061
                                        JANUS_LOG(LOG_VERB, "Setting record property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->recording_active ? "true" : "false", publisher->room->room_id, publisher->user_id);
3062
                                }
3063
                                if(recfile) {
3064
                                        publisher->recording_base = g_strdup(json_string_value(recfile));
3065
                                        JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", publisher->recording_base, publisher->room->room_id, publisher->user_id);
3066
                                }
3067
                                /* Done */
3068
                                session->participant_type = janus_videoroom_p_type_publisher;
3069
                                session->participant = publisher;
3070
                                /* Return a list of all available publishers (those with an SDP available, that is) */
3071
                                json_t *list = json_array();
3072
                                GHashTableIter iter;
3073
                                gpointer value;
3074
                                g_hash_table_insert(videoroom->participants, janus_uint64_dup(publisher->user_id), publisher);
3075
                                g_hash_table_iter_init(&iter, videoroom->participants);
3076
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3077
                                        janus_videoroom_participant *p = value;
3078
                                        if(p == publisher || !p->sdp || !p->session->started) {
3079
                                                continue;
3080
                                        }
3081
                                        json_t *pl = json_object();
3082
                                        json_object_set_new(pl, "id", json_integer(p->user_id));
3083
                                        if(p->display)
3084
                                                json_object_set_new(pl, "display", json_string(p->display));
3085
                                        if(p->audio_level_extmap_id > 0)
3086
                                                json_object_set_new(pl, "talking", p->talking ? json_true() : json_false());
3087
                                        json_array_append_new(list, pl);
3088
                                }
3089
                                janus_mutex_unlock(&videoroom->participants_mutex);
3090
                                event = json_object();
3091
                                json_object_set_new(event, "videoroom", json_string("joined"));
3092
                                json_object_set_new(event, "room", json_integer(videoroom->room_id));
3093
                                json_object_set_new(event, "description", json_string(videoroom->room_name));
3094
                                json_object_set_new(event, "id", json_integer(user_id));
3095
                                json_object_set_new(event, "private_id", json_integer(publisher->pvt_id));
3096
                                json_object_set_new(event, "publishers", list);
3097
                                /* Also notify event handlers */
3098
                                if(notify_events && gateway->events_is_enabled()) {
3099
                                        json_t *info = json_object();
3100
                                        json_object_set_new(info, "event", json_string("joined"));
3101
                                        json_object_set_new(info, "room", json_integer(videoroom->room_id));
3102
                                        json_object_set_new(info, "id", json_integer(user_id));
3103
                                        json_object_set_new(info, "private_id", json_integer(publisher->pvt_id));
3104
                                        if(display_text != NULL)
3105
                                                json_object_set_new(info, "display", json_string(display_text));
3106
                                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
3107
                                }
3108
                        } else if(!strcasecmp(ptype_text, "listener")) {
3109
                                JANUS_LOG(LOG_VERB, "Configuring new listener\n");
3110
                                /* This is a new listener */
3111
                                JANUS_VALIDATE_JSON_OBJECT(root, listener_parameters,
3112
                                        error_code, error_cause, TRUE,
3113
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3114
                                if(error_code != 0)
3115
                                        goto error;
3116
                                json_t *feed = json_object_get(root, "feed");
3117
                                guint64 feed_id = json_integer_value(feed);
3118
                                json_t *pvt = json_object_get(root, "private_id");
3119
                                guint64 pvt_id = json_integer_value(pvt);
3120
                                json_t *audio = json_object_get(root, "audio");
3121
                                json_t *video = json_object_get(root, "video");
3122
                                json_t *data = json_object_get(root, "data");
3123
                                janus_mutex_lock(&videoroom->participants_mutex);
3124
                                janus_videoroom_participant *owner = NULL;
3125
                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &feed_id);
3126
                                janus_mutex_unlock(&videoroom->participants_mutex);
3127
                                if(publisher == NULL || publisher->sdp == NULL) {
3128
                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
3129
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3130
                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
3131
                                        goto error;
3132
                                } else {
3133
                                        /* First of all, let's check if this room requires valid private_id values */
3134
                                        if(videoroom->require_pvtid) {
3135
                                                /* It does, let's make sure this subscription complies */
3136
                                                owner = g_hash_table_lookup(videoroom->private_ids, GUINT_TO_POINTER(pvt_id));
3137
                                                if(pvt_id == 0 || owner == NULL) {
3138
                                                        JANUS_LOG(LOG_ERR, "Unauthorized (this room requires a valid private_id)\n");
3139
                                                        error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
3140
                                                        g_snprintf(error_cause, 512, "Unauthorized (this room requires a valid private_id)");
3141
                                                        goto error;
3142
                                                }
3143
                                        }
3144
                                        janus_videoroom_listener *listener = g_malloc0(sizeof(janus_videoroom_listener));
3145
                                        listener->session = session;
3146
                                        listener->room = videoroom;
3147
                                        listener->feed = publisher;
3148
                                        listener->pvt_id = pvt_id;
3149
                                        /* Initialize the listener context */
3150
                                        janus_rtp_switching_context_reset(&listener->context);
3151
                                        listener->audio = audio ? json_is_true(audio) : TRUE;        /* True by default */
3152
                                        if(!publisher->audio)
3153
                                                listener->audio = FALSE;        /* ... unless the publisher isn't sending any audio */
3154
                                        listener->video = video ? json_is_true(video) : TRUE;        /* True by default */
3155
                                        if(!publisher->video)
3156
                                                listener->video = FALSE;        /* ... unless the publisher isn't sending any video */
3157
                                        listener->data = data ? json_is_true(data) : TRUE;        /* True by default */
3158
                                        if(!publisher->data)
3159
                                                listener->data = FALSE;        /* ... unless the publisher isn't sending any data */
3160
                                        listener->paused = TRUE;        /* We need an explicit start from the listener */
3161
                                        session->participant = listener;
3162
                                        if(videoroom->do_svc) {
3163
                                                /* This listener belongs to a room where VP9 SVC has been enabled,
3164
                                                 * let's assume we're interested in all layers for the time being */
3165
                                                listener->spatial_layer = -1;
3166
                                                listener->target_spatial_layer = 1;                /* FIXME Chrome sends 0 and 1 */
3167
                                                listener->temporal_layer = -1;
3168
                                                listener->target_temporal_layer = 2;        /* FIXME Chrome sends 0, 1 and 2 */
3169
                                        }
3170
                                        janus_mutex_lock(&publisher->listeners_mutex);
3171
                                        publisher->listeners = g_slist_append(publisher->listeners, listener);
3172
                                        janus_mutex_unlock(&publisher->listeners_mutex);
3173
                                        if(owner != NULL) {
3174
                                                janus_mutex_lock(&owner->listeners_mutex);
3175
                                                owner->subscriptions = g_slist_append(owner->subscriptions, listener);
3176
                                                janus_mutex_unlock(&owner->listeners_mutex);
3177
                                        }
3178
                                        event = json_object();
3179
                                        json_object_set_new(event, "videoroom", json_string("attached"));
3180
                                        json_object_set_new(event, "room", json_integer(videoroom->room_id));
3181
                                        json_object_set_new(event, "id", json_integer(feed_id));
3182
                                        if(publisher->display)
3183
                                                json_object_set_new(event, "display", json_string(publisher->display));
3184
                                        session->participant_type = janus_videoroom_p_type_subscriber;
3185
                                        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3186
                                        /* Negotiate by sending the selected publisher SDP back */
3187
                                        if(publisher->sdp != NULL) {
3188
                                                json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", publisher->sdp);
3189
                                                /* How long will the gateway take to push the event? */
3190
                                                g_atomic_int_set(&session->hangingup, 0);
3191
                                                gint64 start = janus_get_monotonic_time();
3192
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
3193
                                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3194
                                                json_decref(event);
3195
                                                json_decref(jsep);
3196
                                                janus_videoroom_message_free(msg);
3197
                                                /* Also notify event handlers */
3198
                                                if(notify_events && gateway->events_is_enabled()) {
3199
                                                        json_t *info = json_object();
3200
                                                        json_object_set_new(info, "event", json_string("subscribing"));
3201
                                                        json_object_set_new(info, "room", json_integer(videoroom->room_id));
3202
                                                        json_object_set_new(info, "feed", json_integer(feed_id));
3203
                                                        json_object_set_new(info, "private_id", json_integer(pvt_id));
3204
                                                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
3205
                                                }
3206
                                                continue;
3207
                                        }
3208
                                }
3209
                        } else {
3210
                                JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
3211
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3212
                                g_snprintf(error_cause, 512, "Invalid element (ptype)");
3213
                                goto error;
3214
                        }
3215
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
3216
                        /* Handle this publisher */
3217
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
3218
                        if(participant == NULL) {
3219
                                JANUS_LOG(LOG_ERR, "Invalid participant instance\n");
3220
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3221
                                g_snprintf(error_cause, 512, "Invalid participant instance");
3222
                                goto error;
3223
                        }
3224
                        if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")) {
3225
                                JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
3226
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
3227
                                g_snprintf(error_cause, 512, "Already in as a publisher on this handle");
3228
                                goto error;
3229
                        } else if(!strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish")) {
3230
                                if(!strcasecmp(request_text, "publish") && participant->sdp) {
3231
                                        JANUS_LOG(LOG_ERR, "Can't publish, already published\n");
3232
                                        error_code = JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED;
3233
                                        g_snprintf(error_cause, 512, "Can't publish, already published");
3234
                                        goto error;
3235
                                }
3236
                                if(participant->kicked) {
3237
                                        JANUS_LOG(LOG_ERR, "Unauthorized, you have been kicked\n");
3238
                                        error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
3239
                                        g_snprintf(error_cause, 512, "Unauthorized, you have been kicked");
3240
                                        goto error;
3241
                                }
3242
                                /* Configure (or publish a new feed) audio/video/bitrate for this publisher */
3243
                                JANUS_VALIDATE_JSON_OBJECT(root, publish_parameters,
3244
                                        error_code, error_cause, TRUE,
3245
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3246
                                if(error_code != 0)
3247
                                        goto error;
3248
                                json_t *audio = json_object_get(root, "audio");
3249
                                json_t *video = json_object_get(root, "video");
3250
                                json_t *data = json_object_get(root, "data");
3251
                                json_t *bitrate = json_object_get(root, "bitrate");
3252
                                json_t *record = json_object_get(root, "record");
3253
                                json_t *recfile = json_object_get(root, "filename");
3254
                                json_t *display = json_object_get(root, "display");
3255
                                if(audio) {
3256
                                        gboolean audio_active = json_is_true(audio);
3257
                                        if(session->started && audio_active && !participant->audio_active) {
3258
                                                /* Audio was just resumed, try resetting the RTP headers for viewers */
3259
                                                janus_mutex_lock(&participant->listeners_mutex);
3260
                                                GSList *ps = participant->listeners;
3261
                                                while(ps) {
3262
                                                        janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
3263
                                                        if(l)
3264
                                                                l->context.a_seq_reset = TRUE;
3265
                                                        ps = ps->next;
3266
                                                }
3267
                                                janus_mutex_unlock(&participant->listeners_mutex);
3268
                                        }
3269
                                        participant->audio_active = audio_active;
3270
                                        JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", participant->audio_active ? "true" : "false", participant->room->room_id, participant->user_id);
3271
                                }
3272
                                if(video) {
3273
                                        gboolean video_active = json_is_true(video);
3274
                                        if(session->started && video_active && !participant->video_active) {
3275
                                                /* Video was just resumed, try resetting the RTP headers for viewers */
3276
                                                janus_mutex_lock(&participant->listeners_mutex);
3277
                                                GSList *ps = participant->listeners;
3278
                                                while(ps) {
3279
                                                        janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
3280
                                                        if(l)
3281
                                                                l->context.v_seq_reset = TRUE;
3282
                                                        ps = ps->next;
3283
                                                }
3284
                                                janus_mutex_unlock(&participant->listeners_mutex);
3285
                                        }
3286
                                        participant->video_active = video_active;
3287
                                        JANUS_LOG(LOG_VERB, "Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", participant->video_active ? "true" : "false", participant->room->room_id, participant->user_id);
3288
                                }
3289
                                if(data) {
3290
                                        gboolean data_active = json_is_true(data);
3291
                                        participant->data_active = data_active;
3292
                                        JANUS_LOG(LOG_VERB, "Setting data property: %s (room %"SCNu64", user %"SCNu64")\n", participant->data_active ? "true" : "false", participant->room->room_id, participant->user_id);
3293
                                }
3294
                                if(bitrate) {
3295
                                        participant->bitrate = json_integer_value(bitrate);
3296
                                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
3297
                                        /* Send a new REMB */
3298
                                        if(session->started)
3299
                                                participant->remb_latest = janus_get_monotonic_time();
3300
                                        char rtcpbuf[24];
3301
                                        janus_rtcp_remb((char *)(&rtcpbuf), 24, participant->bitrate ? participant->bitrate : 256*1024);
3302
                                        gateway->relay_rtcp(msg->handle, 1, rtcpbuf, 24);
3303
                                }
3304
                                janus_mutex_lock(&participant->rec_mutex);
3305
                                gboolean prev_recording_active = participant->recording_active;
3306
                                if(record) {
3307
                                        participant->recording_active = json_is_true(record);
3308
                                        JANUS_LOG(LOG_VERB, "Setting record property: %s (room %"SCNu64", user %"SCNu64")\n", participant->recording_active ? "true" : "false", participant->room->room_id, participant->user_id);
3309
                                }
3310
                                if(recfile) {
3311
                                        participant->recording_base = g_strdup(json_string_value(recfile));
3312
                                        JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", participant->recording_base, participant->room->room_id, participant->user_id);
3313
                                }
3314
                                /* Do we need to do something with the recordings right now? */
3315
                                if(participant->recording_active != prev_recording_active) {
3316
                                        /* Something changed */
3317
                                        if(!participant->recording_active) {
3318
                                                /* Not recording (anymore?) */
3319
                                                janus_videoroom_recorder_close(participant);
3320
                                        } else if(participant->recording_active && participant->sdp) {
3321
                                                /* We've started recording, send a PLI/FIR and go on */
3322
                                                janus_videoroom_recorder_create(
3323
                                                        participant, strstr(participant->sdp, "m=audio") != NULL,
3324
                                                        strstr(participant->sdp, "m=video") != NULL,
3325
                                                        strstr(participant->sdp, "m=application") != NULL);
3326
                                                if(strstr(participant->sdp, "m=video")) {
3327
                                                        /* Send a FIR */
3328
                                                        char buf[20];
3329
                                                        memset(buf, 0, 20);
3330
                                                        janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
3331
                                                        JANUS_LOG(LOG_VERB, "Recording video, sending FIR to %"SCNu64" (%s)\n",
3332
                                                                participant->user_id, participant->display ? participant->display : "??");
3333
                                                        gateway->relay_rtcp(participant->session->handle, 1, buf, 20);
3334
                                                        /* Send a PLI too, just in case... */
3335
                                                        janus_rtcp_pli((char *)&buf, 12);
3336
                                                        JANUS_LOG(LOG_VERB, "Recording video, sending PLI to %"SCNu64" (%s)\n",
3337
                                                                participant->user_id, participant->display ? participant->display : "??");
3338
                                                        gateway->relay_rtcp(participant->session->handle, 1, buf, 12);
3339
                                                }
3340
                                        }
3341
                                }
3342
                                janus_mutex_unlock(&participant->rec_mutex);
3343
                                if(display) {
3344
                                        janus_mutex_lock(&participant->room->participants_mutex);
3345
                                        char *old_display = participant->display;
3346
                                        char *new_display = g_strdup(json_string_value(display));
3347
                                        participant->display = new_display;
3348
                                        g_free(old_display);
3349
                                        json_t *display_event = json_object();
3350
                                        json_object_set_new(display_event, "videoroom", json_string("event"));
3351
                                        json_object_set_new(display_event, "id", json_integer(participant->user_id));
3352
                                        json_object_set_new(display_event, "display", json_string(participant->display));
3353
                                        if(participant->room && !participant->room->destroyed) {
3354
                                                janus_videoroom_notify_participants(participant, display_event);
3355
                                        }
3356
                                        janus_mutex_unlock(&participant->room->participants_mutex);
3357
                                        json_decref(display_event);
3358
                                }
3359
                                /* Done */
3360
                                event = json_object();
3361
                                json_object_set_new(event, "videoroom", json_string("event"));
3362
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
3363
                                json_object_set_new(event, "configured", json_string("ok"));
3364
                                /* Also notify event handlers */
3365
                                if(notify_events && gateway->events_is_enabled()) {
3366
                                        json_t *info = json_object();
3367
                                        json_object_set_new(info, "event", json_string("configured"));
3368
                                        json_object_set_new(info, "room", json_integer(participant->room->room_id));
3369
                                        json_object_set_new(info, "id", json_integer(participant->user_id));
3370
                                        json_object_set_new(info, "audio_active", participant->audio_active ? json_true() : json_false());
3371
                                        json_object_set_new(info, "video_active", participant->video_active ? json_true() : json_false());
3372
                                        json_object_set_new(info, "data_active", participant->data_active ? json_true() : json_false());
3373
                                        json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
3374
                                        if(participant->arc || participant->vrc || participant->drc) {
3375
                                                json_t *recording = json_object();
3376
                                                if(participant->arc && participant->arc->filename)
3377
                                                        json_object_set_new(recording, "audio", json_string(participant->arc->filename));
3378
                                                if(participant->vrc && participant->vrc->filename)
3379
                                                        json_object_set_new(recording, "video", json_string(participant->vrc->filename));
3380
                                                if(participant->drc && participant->drc->filename)
3381
                                                        json_object_set_new(recording, "data", json_string(participant->drc->filename));
3382
                                                json_object_set_new(info, "recording", recording);
3383
                                        }
3384
                                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
3385
                                }
3386
                        } else if(!strcasecmp(request_text, "unpublish")) {
3387
                                /* This participant wants to unpublish */
3388
                                if(!participant->sdp) {
3389
                                        JANUS_LOG(LOG_ERR, "Can't unpublish, not published\n");
3390
                                        error_code = JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED;
3391
                                        g_snprintf(error_cause, 512, "Can't unpublish, not published");
3392
                                        goto error;
3393
                                }
3394
                                /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
3395
                                gateway->close_pc(session->handle);
3396
                                /* Done */
3397
                                event = json_object();
3398
                                json_object_set_new(event, "videoroom", json_string("event"));
3399
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
3400
                                json_object_set_new(event, "unpublished", json_string("ok"));
3401
                        } else if(!strcasecmp(request_text, "leave")) {
3402
                                /* Prepare an event to confirm the request */
3403
                                event = json_object();
3404
                                json_object_set_new(event, "videoroom", json_string("event"));
3405
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
3406
                                json_object_set_new(event, "leaving", json_string("ok"));
3407
                                /* This publisher is leaving, tell everybody */
3408
                                session->participant_type = janus_videoroom_p_type_none;
3409
                                janus_videoroom_leave_or_unpublish(participant, TRUE, FALSE);
3410
                                /* Done */
3411
                                participant->audio_active = FALSE;
3412
                                participant->video_active = FALSE;
3413
                                participant->data_active = FALSE;
3414
                                session->started = FALSE;
3415
                                //~ session->destroy = TRUE;
3416
                        } else {
3417
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
3418
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
3419
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
3420
                                goto error;
3421
                        }
3422
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
3423
                        /* Handle this listener */
3424
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
3425
                        if(listener == NULL) {
3426
                                JANUS_LOG(LOG_ERR, "Invalid listener instance\n");
3427
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3428
                                g_snprintf(error_cause, 512, "Invalid listener instance");
3429
                                goto error;
3430
                        }
3431
                        if(!strcasecmp(request_text, "join")) {
3432
                                JANUS_LOG(LOG_ERR, "Already in as a listener on this handle\n");
3433
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
3434
                                g_snprintf(error_cause, 512, "Already in as a listener on this handle");
3435
                                goto error;
3436
                        } else if(!strcasecmp(request_text, "start")) {
3437
                                /* Start/restart receiving the publisher streams */
3438
                                janus_videoroom_participant *publisher = listener->feed;
3439
                                listener->paused = FALSE;
3440
                                event = json_object();
3441
                                json_object_set_new(event, "videoroom", json_string("event"));
3442
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3443
                                json_object_set_new(event, "started", json_string("ok"));
3444
                                if(publisher) {
3445
                                        /* Send a FIR */
3446
                                        char buf[20];
3447
                                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
3448
                                        JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3449
                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
3450
                                        /* Send a PLI too, just in case... */
3451
                                        janus_rtcp_pli((char *)&buf, 12);
3452
                                        JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3453
                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
3454
                                }
3455
                        } else if(!strcasecmp(request_text, "configure")) {
3456
                                JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
3457
                                        error_code, error_cause, TRUE,
3458
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3459
                                if(error_code != 0)
3460
                                        goto error;
3461
                                if(listener->kicked) {
3462
                                        JANUS_LOG(LOG_ERR, "Unauthorized, you have been kicked\n");
3463
                                        error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
3464
                                        g_snprintf(error_cause, 512, "Unauthorized, you have been kicked");
3465
                                        goto error;
3466
                                }
3467
                                json_t *audio = json_object_get(root, "audio");
3468
                                json_t *video = json_object_get(root, "video");
3469
                                json_t *data = json_object_get(root, "data");
3470
                                json_t *spatial = json_object_get(root, "spatial_layer");
3471
                                json_t *temporal = json_object_get(root, "temporal_layer");
3472
                                /* Update the audio/video/data flags, if set */
3473
                                janus_videoroom_participant *publisher = listener->feed;
3474
                                if(publisher) {
3475
                                        if(audio && publisher->audio)
3476
                                                listener->audio = json_is_true(audio);
3477
                                        if(video && publisher->video)
3478
                                                listener->video = json_is_true(video);
3479
                                        if(data && publisher->data)
3480
                                                listener->data = json_is_true(data);
3481
                                }
3482
                                if(listener->room->do_svc) {
3483
                                        /* Also check if the viewer is trying to configure a layer change */
3484
                                        if(spatial) {
3485
                                                int spatial_layer = json_integer_value(spatial);
3486
                                                if(spatial_layer > 1) {
3487
                                                        JANUS_LOG(LOG_WARN, "Spatial layer higher than 1, will probably be ignored\n");
3488
                                                }
3489
                                                if(spatial_layer != listener->target_spatial_layer) {
3490
                                                        /* Send a FIR to the new RTP forward publisher */
3491
                                                        char buf[20];
3492
                                                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
3493
                                                        JANUS_LOG(LOG_VERB, "Need to downscale spatially, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3494
                                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
3495
                                                        /* Send a PLI too, just in case... */
3496
                                                        janus_rtcp_pli((char *)&buf, 12);
3497
                                                        JANUS_LOG(LOG_VERB, "Need to downscale spatially, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3498
                                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
3499
                                                }
3500
                                                listener->target_spatial_layer = spatial_layer;
3501
                                        }
3502
                                        if(temporal) {
3503
                                                int temporal_layer = json_integer_value(temporal);
3504
                                                if(temporal_layer > 2) {
3505
                                                        JANUS_LOG(LOG_WARN, "Temporal layer higher than 2, will probably be ignored\n");
3506
                                                }
3507
                                                listener->target_temporal_layer = temporal_layer;
3508
                                        }
3509
                                }
3510
                                event = json_object();
3511
                                json_object_set_new(event, "videoroom", json_string("event"));
3512
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3513
                                json_object_set_new(event, "configured", json_string("ok"));
3514
                        } else if(!strcasecmp(request_text, "pause")) {
3515
                                /* Stop receiving the publisher streams for a while */
3516
                                listener->paused = TRUE;
3517
                                event = json_object();
3518
                                json_object_set_new(event, "videoroom", json_string("event"));
3519
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3520
                                json_object_set_new(event, "paused", json_string("ok"));
3521
                        } else if(!strcasecmp(request_text, "switch")) {
3522
                                /* This listener wants to switch to a different publisher */
3523
                                JANUS_VALIDATE_JSON_OBJECT(root, listener_parameters,
3524
                                        error_code, error_cause, TRUE,
3525
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3526
                                if(error_code != 0)
3527
                                        goto error;
3528
                                json_t *feed = json_object_get(root, "feed");
3529
                                guint64 feed_id = json_integer_value(feed);
3530
                                json_t *audio = json_object_get(root, "audio");
3531
                                json_t *video = json_object_get(root, "video");
3532
                                json_t *data = json_object_get(root, "data");
3533
                                if(!listener->room) {
3534
                                        JANUS_LOG(LOG_ERR, "Room Destroyed \n");
3535
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3536
                                        g_snprintf(error_cause, 512, "No such room ");
3537
                                        goto error;
3538
                                }
3539
                                if(listener->room->destroyed) {
3540
                                        JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")\n", listener->room->room_id);
3541
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3542
                                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", listener->room->room_id);
3543
                                        goto error;
3544
                                }
3545
                                janus_mutex_lock(&listener->room->participants_mutex);
3546
                                janus_videoroom_participant *publisher = g_hash_table_lookup(listener->room->participants, &feed_id);
3547
                                janus_mutex_unlock(&listener->room->participants_mutex);
3548
                                if(publisher == NULL || publisher->sdp == NULL) {
3549
                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
3550
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3551
                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
3552
                                        goto error;
3553
                                }
3554
                                gboolean paused = listener->paused;
3555
                                listener->paused = TRUE;
3556
                                /* Unsubscribe from the previous publisher */
3557
                                janus_videoroom_participant *prev_feed = listener->feed;
3558
                                if(prev_feed) {
3559
                                        janus_mutex_lock(&prev_feed->listeners_mutex);
3560
                                        prev_feed->listeners = g_slist_remove(prev_feed->listeners, listener);
3561
                                        janus_mutex_unlock(&prev_feed->listeners_mutex);
3562
                                        listener->feed = NULL;
3563
                                }
3564
                                /* Subscribe to the new one */
3565
                                listener->audio = audio ? json_is_true(audio) : TRUE;        /* True by default */
3566
                                if(!publisher->audio)
3567
                                        listener->audio = FALSE;        /* ... unless the publisher isn't sending any audio */
3568
                                listener->video = video ? json_is_true(video) : TRUE;        /* True by default */
3569
                                if(!publisher->video)
3570
                                        listener->video = FALSE;        /* ... unless the publisher isn't sending any video */
3571
                                listener->data = data ? json_is_true(data) : TRUE;        /* True by default */
3572
                                if(!publisher->data)
3573
                                        listener->data = FALSE;        /* ... unless the publisher isn't sending any data */
3574
                                if(listener->room && listener->room->do_svc) {
3575
                                        /* This listener belongs to a room where VP9 SVC has been enabled,
3576
                                         * let's assume we're interested in all layers for the time being */
3577
                                        listener->spatial_layer = -1;
3578
                                        listener->target_spatial_layer = 1;                /* FIXME Chrome sends 0 and 1 */
3579
                                        listener->temporal_layer = -1;
3580
                                        listener->target_temporal_layer = 2;        /* FIXME Chrome sends 0, 1 and 2 */
3581
                                }
3582
                                janus_mutex_lock(&publisher->listeners_mutex);
3583
                                publisher->listeners = g_slist_append(publisher->listeners, listener);
3584
                                janus_mutex_unlock(&publisher->listeners_mutex);
3585
                                listener->feed = publisher;
3586
                                /* Send a FIR to the new publisher */
3587
                                char buf[20];
3588
                                janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
3589
                                JANUS_LOG(LOG_VERB, "Switching existing listener to new publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3590
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
3591
                                /* Send a PLI too, just in case... */
3592
                                janus_rtcp_pli((char *)&buf, 12);
3593
                                JANUS_LOG(LOG_VERB, "Switching existing listener to new publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3594
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
3595
                                /* Done */
3596
                                listener->paused = paused;
3597
                                event = json_object();
3598
                                json_object_set_new(event, "videoroom", json_string("event"));
3599
                                json_object_set_new(event, "switched", json_string("ok"));
3600
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3601
                                json_object_set_new(event, "id", json_integer(feed_id));
3602
                                if(publisher->display)
3603
                                        json_object_set_new(event, "display", json_string(publisher->display));
3604
                                /* Also notify event handlers */
3605
                                if(notify_events && gateway->events_is_enabled()) {
3606
                                        json_t *info = json_object();
3607
                                        json_object_set_new(info, "event", json_string("switched"));
3608
                                        json_object_set_new(info, "room", json_integer(publisher->room->room_id));
3609
                                        json_object_set_new(info, "feed", json_integer(publisher->user_id));
3610
                                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
3611
                                }
3612
                        } else if(!strcasecmp(request_text, "leave")) {
3613
                                janus_videoroom_participant *publisher = listener->feed;
3614
                                if(publisher != NULL) {
3615
                                        janus_mutex_lock(&publisher->listeners_mutex);
3616
                                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
3617
                                        janus_mutex_unlock(&publisher->listeners_mutex);
3618
                                        listener->feed = NULL;
3619
                                }
3620
                                if(listener->pvt_id > 0) {
3621
                                        janus_videoroom_participant *owner = g_hash_table_lookup(listener->room->private_ids, GUINT_TO_POINTER(listener->pvt_id));
3622
                                        if(owner != NULL) {
3623
                                                janus_mutex_lock(&owner->listeners_mutex);
3624
                                                owner->subscriptions = g_slist_remove(owner->subscriptions, listener);
3625
                                                janus_mutex_unlock(&owner->listeners_mutex);
3626
                                        }
3627
                                }
3628
                                session->participant_type = janus_videoroom_p_type_none;
3629
                                event = json_object();
3630
                                json_object_set_new(event, "videoroom", json_string("event"));
3631
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3632
                                json_object_set_new(event, "left", json_string("ok"));
3633
                                session->started = FALSE;
3634
                        } else {
3635
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
3636
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
3637
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
3638
                                goto error;
3639
                        }
3640
                }
3641

    
3642
                /* Prepare JSON event */
3643
                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3644
                /* Any SDP to handle? */
3645
                const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
3646
                const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
3647
                if(!msg_sdp) {
3648
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3649
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3650
                        json_decref(event);
3651
                } else {
3652
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
3653
                        const char *type = NULL;
3654
                        if(!strcasecmp(msg_sdp_type, "offer")) {
3655
                                /* We need to answer */
3656
                                type = "answer";
3657
                        } else if(!strcasecmp(msg_sdp_type, "answer")) {
3658
                                /* We got an answer (from a listener?), no need to negotiate */
3659
                                g_atomic_int_set(&session->hangingup, 0);
3660
                                int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3661
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3662
                                json_decref(event);
3663
                                janus_videoroom_message_free(msg);
3664
                                continue;
3665
                        } else {
3666
                                /* TODO We don't support anything else right now... */
3667
                                JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg_sdp_type);
3668
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3669
                                g_snprintf(error_cause, 512, "Unknown SDP type '%s'", msg_sdp_type);
3670
                                goto error;
3671
                        }
3672
                        if(session->participant_type != janus_videoroom_p_type_publisher) {
3673
                                /* We shouldn't be here, we always offer ourselves */
3674
                                JANUS_LOG(LOG_ERR, "Only publishers send offers\n");
3675
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3676
                                g_snprintf(error_cause, 512, "Only publishers send offers");
3677
                                goto error;
3678
                        } else {
3679
                                /* This is a new publisher: is there room? */
3680
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
3681
                                janus_videoroom *videoroom = participant->room;
3682
                                int count = 0;
3683
                                GHashTableIter iter;
3684
                                gpointer value;
3685
                                if(!videoroom) {
3686
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3687
                                        goto error;
3688
                                }
3689
                                if(videoroom->destroyed) {
3690
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3691
                                        goto error;
3692
                                }
3693
                                janus_mutex_lock(&videoroom->participants_mutex);
3694
                                g_hash_table_iter_init(&iter, videoroom->participants);
3695
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3696
                                        janus_videoroom_participant *p = value;
3697
                                        if(p != participant && p->sdp)
3698
                                                count++;
3699
                                }
3700
                                janus_mutex_unlock(&videoroom->participants_mutex);
3701
                                if(count == videoroom->max_publishers) {
3702
                                        participant->audio_active = FALSE;
3703
                                        participant->video_active = FALSE;
3704
                                        participant->data_active = FALSE;
3705
                                        JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
3706
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3707
                                        g_snprintf(error_cause, 512, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
3708
                                        goto error;
3709
                                }
3710
                                /* Now prepare the SDP to give back */
3711
                                if(strstr(msg_sdp, "Mozilla")) {
3712
                                        participant->firefox = TRUE;
3713
                                }
3714
                                /* Start by parsing the offer */
3715
                                char error_str[512];
3716
                                janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str));
3717
                                if(offer == NULL) {
3718
                                        json_decref(event);
3719
                                        JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str);
3720
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP;
3721
                                        g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str);
3722
                                        goto error;
3723
                                }
3724
                                gboolean audio_level_extmap = FALSE, video_orient_extmap = FALSE, playout_delay_extmap = FALSE;
3725
                                janus_sdp_mdirection audio_level_mdir = JANUS_SDP_SENDRECV,
3726
                                        video_orient_mdir = JANUS_SDP_SENDRECV,
3727
                                        playout_delay_mdir = JANUS_SDP_SENDRECV;
3728
                                GList *temp = offer->m_lines;
3729
                                while(temp) {
3730
                                        /* Which media are available? */
3731
                                        janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
3732
                                        if(m->type == JANUS_SDP_AUDIO && m->port > 0) {
3733
                                                participant->audio = TRUE;
3734
                                        } else if(m->type == JANUS_SDP_VIDEO && m->port > 0) {
3735
                                                participant->video = TRUE;
3736
                                        } else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
3737
                                                participant->data = TRUE;
3738
                                        }
3739
                                        if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) {
3740
                                                /* Are the extmaps we care about there? */
3741
                                                GList *ma = m->attributes;
3742
                                                while(ma) {
3743
                                                        janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
3744
                                                        if(a->value) {
3745
                                                                if(videoroom->audiolevel_ext && m->type == JANUS_SDP_AUDIO && strstr(a->value, JANUS_RTP_EXTMAP_AUDIO_LEVEL)) {
3746
                                                                        participant->audio_level_extmap_id = atoi(a->value);
3747
                                                                        audio_level_extmap = TRUE;
3748
                                                                        audio_level_mdir = a->direction;
3749
                                                                } else if(videoroom->videoorient_ext && m->type == JANUS_SDP_VIDEO && strstr(a->value, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION)) {
3750
                                                                        participant->video_orient_extmap_id = atoi(a->value);
3751
                                                                        video_orient_extmap = TRUE;
3752
                                                                        video_orient_mdir = a->direction;
3753
                                                                } else if(videoroom->playoutdelay_ext && m->type == JANUS_SDP_VIDEO && strstr(a->value, JANUS_RTP_EXTMAP_PLAYOUT_DELAY)) {
3754
                                                                        participant->playout_delay_extmap_id = atoi(a->value);
3755
                                                                        playout_delay_extmap = TRUE;
3756
                                                                        playout_delay_mdir = a->direction;
3757
                                                                }
3758
                                                        }
3759
                                                        ma = ma->next;
3760
                                                }
3761
                                        }
3762
                                        temp = temp->next;
3763
                                }
3764
                                /* Prepare an answer now: force the room codecs and recvonly on the Janus side */
3765
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", participant->audio ? "is" : "is NOT");
3766
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", participant->video ? "is" : "is NOT");
3767
                                JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", participant->data ? "is" : "is NOT");
3768
                                janus_sdp *answer = janus_sdp_generate_answer(offer,
3769
                                        JANUS_SDP_OA_AUDIO_CODEC, janus_videoroom_audiocodec_name(videoroom->acodec),
3770
                                        JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_RECVONLY,
3771
                                        JANUS_SDP_OA_VIDEO_CODEC, janus_videoroom_videocodec_name(videoroom->vcodec),
3772
                                        JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY,
3773
                                        JANUS_SDP_OA_DONE);
3774
                                janus_sdp_free(offer);
3775
                                /* Replace the session name */
3776
                                g_free(answer->s_name);
3777
                                char s_name[100];
3778
                                g_snprintf(s_name, sizeof(s_name), "VideoRoom %"SCNu64, videoroom->room_id);
3779
                                answer->s_name = g_strdup(s_name);
3780
                                /* Which media are REALLY available? (some may have been rejected) */
3781
                                participant->audio = FALSE;
3782
                                participant->video = FALSE;
3783
                                participant->data = FALSE;
3784
                                temp = answer->m_lines;
3785
                                while(temp) {
3786
                                        janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
3787
                                        if(m->type == JANUS_SDP_AUDIO && m->port > 0) {
3788
                                                participant->audio = TRUE;
3789
                                        } else if(m->type == JANUS_SDP_VIDEO && m->port > 0) {
3790
                                                participant->video = TRUE;
3791
                                        } else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
3792
                                                participant->data = TRUE;
3793
                                        }
3794
                                        temp = temp->next;
3795
                                }
3796
                                JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to send an audio stream\n", participant->audio ? "is" : "is NOT");
3797
                                JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to send a video stream\n", participant->video ? "is" : "is NOT");
3798
                                JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to open a data channel\n", participant->data ? "is" : "is NOT");
3799
                                /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
3800
                                if(participant->firefox) {        /* Don't add any b=AS attribute for Chrome */
3801
                                        janus_sdp_mline *m = janus_sdp_mline_find(answer, JANUS_SDP_VIDEO);
3802
                                        if(m != NULL && videoroom->bitrate > 0) {
3803
                                                m->b_name = g_strdup("AS");
3804
                                                m->b_value = (int)(videoroom->bitrate/1000);
3805
                                        }
3806
                                }
3807
                                /* Add the extmap attributes, if needed */
3808
                                if(audio_level_extmap) {
3809
                                        /* First of all, let's check if the extmap attribute had a direction */
3810
                                        const char *direction = NULL;
3811
                                        switch(audio_level_mdir) {
3812
                                                case JANUS_SDP_SENDONLY:
3813
                                                        direction = "/recvonly";
3814
                                                        break;
3815
                                                case JANUS_SDP_RECVONLY:
3816
                                                case JANUS_SDP_INACTIVE:
3817
                                                        direction = "/inactive";
3818
                                                        break;
3819
                                                default:
3820
                                                        direction = "";
3821
                                                        break;
3822
                                        }
3823
                                        janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
3824
                                                "%d%s %s\r\n", participant->audio_level_extmap_id, direction, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
3825
                                        janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_AUDIO), a);
3826
                                }
3827
                                if(video_orient_extmap) {
3828
                                        /* First of all, let's check if the extmap attribute had a direction */
3829
                                        const char *direction = NULL;
3830
                                        switch(video_orient_mdir) {
3831
                                                case JANUS_SDP_SENDONLY:
3832
                                                        direction = "/recvonly";
3833
                                                        break;
3834
                                                case JANUS_SDP_RECVONLY:
3835
                                                case JANUS_SDP_INACTIVE:
3836
                                                        direction = "/inactive";
3837
                                                        break;
3838
                                                default:
3839
                                                        direction = "";
3840
                                                        break;
3841
                                        }
3842
                                        janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
3843
                                                "%d%s %s\r\n", participant->video_orient_extmap_id, direction, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
3844
                                        janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_VIDEO), a);
3845
                                }
3846
                                if(playout_delay_extmap) {
3847
                                        /* First of all, let's check if the extmap attribute had a direction */
3848
                                        const char *direction = NULL;
3849
                                        switch(playout_delay_mdir) {
3850
                                                case JANUS_SDP_SENDONLY:
3851
                                                        direction = "/recvonly";
3852
                                                        break;
3853
                                                case JANUS_SDP_RECVONLY:
3854
                                                case JANUS_SDP_INACTIVE:
3855
                                                        direction = "/inactive";
3856
                                                        break;
3857
                                                default:
3858
                                                        direction = "";
3859
                                                        break;
3860
                                        }
3861
                                        janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
3862
                                                "%d%s %s\r\n", participant->playout_delay_extmap_id, direction, JANUS_RTP_EXTMAP_PLAYOUT_DELAY);
3863
                                        janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_VIDEO), a);
3864
                                }
3865
                                /* Generate an SDP string we can send back to the publisher */
3866
                                char *answer_sdp = janus_sdp_write(answer);
3867
                                /* Now turn the SDP into what we'll send subscribers, using the static payload types for making switching easier */
3868
                                offer = janus_sdp_generate_offer(s_name, answer->c_addr,
3869
                                        JANUS_SDP_OA_AUDIO, participant->audio,