Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ a27e90d3

History | View | Annotate | Download (194 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
publishers = <max number of concurrent senders> (e.g., 6 for a video
62
             conference or 1 for a webinar)
63
bitrate = <max video bitrate for senders> (e.g., 128000)
64
fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
65
audiocodec = opus|isac32|isac16|pcmu|pcma (audio codec to force on publishers, default=opus)
66
videocodec = vp8|vp9|h264 (video codec to force on publishers, default=vp8)
67
audiolevel_ext = yes|no (whether the ssrc-audio-level RTP extension must be
68
        negotiated/used or not for new publishers, default=yes)
69
videoorientation_ext = yes|no (whether the video-orientation RTP extension must be
70
        negotiated/used or not for new publishers, default=yes)
71
playoutdelay_ext = yes|no (whether the playout-delay RTP extension must be
72
        negotiated/used or not for new publishers, default=yes)
73
record = true|false (whether this room should be recorded, default=false)
74
rec_dir = <folder where recordings should be stored, when enabled>
75
\endverbatim
76
 *
77
 * Note that recording will work with all codecs except iSAC.
78
 *
79
 * \section sfuapi Video Room API
80
 * 
81
 * The Video Room API supports several requests, some of which are
82
 * synchronous and some asynchronous. There are some situations, though,
83
 * (invalid JSON, invalid request) which will always result in a
84
 * synchronous error response even for asynchronous requests. 
85
 * 
86
 * \c create , \c destroy , \c exists, \c list, \c allowed, \c kick and
87
 * and \c listparticipants are synchronous requests, which means you'll
88
 * get a response directly within the context of the transaction.
89
 * \c create allows you to create a new video room dynamically, as an
90
 * alternative to using the configuration file; \c destroy removes a
91
 * video room and destroys it, kicking all the users out as part of the
92
 * process; \c exists allows you to check whether a specific video room
93
 * exists; finally, \c list lists all the available rooms, while \c
94
 * listparticipants lists all the participants of a specific room and
95
 * their details.
96
 * 
97
 * The \c join , \c joinandconfigure , \c configure , \c publish ,
98
 * \c unpublish , \c start , \c pause , \c switch , \c stop , \c add ,
99
 * \c remove and \c leave requests instead are all asynchronous, which
100
 * means you'll get a notification about their success or failure in
101
 * an event. \c join allows you to join a specific video room, specifying
102
 * whether that specific PeerConnection will be used for publishing or
103
 * watching; \c configure can be used to modify some of the participation
104
 * settings (e.g., bitrate cap); \c joinandconfigure combines the previous
105
 * two requests in a single one (just for publishers); \c publish can be
106
 * used to start sending media to broadcast to the other participants,
107
 * while \c unpublish does the opposite; \c start allows you to start
108
 * receiving media from a publisher you've subscribed to previously by
109
 * means of a \c join , while \c pause pauses the delivery of the media;
110
 * the \c switch request can be used to change the source of the media
111
 * flowing over a specific PeerConnection (e.g., I was watching Alice,
112
 * I want to watch Bob now) without having to create a new handle for
113
 * that; \c stop interrupts a viewer instance; \c add and \c remove
114
 * are just used when involving "Plan B", and are used to add or remove
115
 * publishers to be muxed in the single viewer PeerConnection; finally,
116
 * \c leave allows you to leave a video room for good.
117
 * 
118
 * Notice that, in general, all users can create rooms. If you want to
119
 * limit this functionality, you can configure an admin \c admin_key in
120
 * the plugin settings. When configured, only "create" requests that
121
 * include the correct \c admin_key value in an "admin_key" property
122
 * will succeed, and will be rejected otherwise.
123
 * 
124
 * Actual API docs: TBD.
125
 * 
126
 * \ingroup plugins
127
 * \ref plugins
128
 */
129

    
130
#include "plugin.h"
131

    
132
#include <jansson.h>
133

    
134
#include "../debug.h"
135
#include "../apierror.h"
136
#include "../config.h"
137
#include "../mutex.h"
138
#include "../rtp.h"
139
#include "../rtcp.h"
140
#include "../record.h"
141
#include "../sdp-utils.h"
142
#include "../utils.h"
143
#include <sys/types.h>
144
#include <sys/socket.h>
145

    
146

    
147
/* Plugin information */
148
#define JANUS_VIDEOROOM_VERSION                        8
149
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.8"
150
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing SFU (Selective Forwarding Unit) for Janus, that is an audio/video router."
151
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
152
#define JANUS_VIDEOROOM_AUTHOR                        "Meetecho s.r.l."
153
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
154

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

    
177
/* Plugin setup */
178
static janus_plugin janus_videoroom_plugin =
179
        JANUS_PLUGIN_INIT (
180
                .init = janus_videoroom_init,
181
                .destroy = janus_videoroom_destroy,
182

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

    
203
/* Plugin creator */
204
janus_plugin *create(void) {
205
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
206
        return &janus_videoroom_plugin;
207
}
208

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

    
306
/* Static configuration instance */
307
static janus_config *config = NULL;
308
static const char *config_folder = NULL;
309
static janus_mutex config_mutex;
310

    
311
/* Useful stuff */
312
static volatile gint initialized = 0, stopping = 0;
313
static gboolean notify_events = TRUE;
314
static janus_callbacks *gateway = NULL;
315
static GThread *handler_thread;
316
static GThread *watchdog;
317
static void *janus_videoroom_handler(void *data);
318
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
319
static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data);
320

    
321
typedef enum janus_videoroom_p_type {
322
        janus_videoroom_p_type_none = 0,
323
        janus_videoroom_p_type_subscriber,                        /* Generic listener/subscriber */
324
        janus_videoroom_p_type_subscriber_muxed,        /* Multiplexed listener/subscriber */
325
        janus_videoroom_p_type_publisher,                        /* Participant/publisher */
326
} janus_videoroom_p_type;
327

    
328
typedef struct janus_videoroom_message {
329
        janus_plugin_session *handle;
330
        char *transaction;
331
        json_t *message;
332
        json_t *jsep;
333
} janus_videoroom_message;
334
static GAsyncQueue *messages = NULL;
335
static janus_videoroom_message exit_message;
336

    
337
static void janus_videoroom_message_free(janus_videoroom_message *msg) {
338
        if(!msg || msg == &exit_message)
339
                return;
340

    
341
        msg->handle = NULL;
342

    
343
        g_free(msg->transaction);
344
        msg->transaction = NULL;
345
        if(msg->message)
346
                json_decref(msg->message);
347
        msg->message = NULL;
348
        if(msg->jsep)
349
                json_decref(msg->jsep);
350
        msg->jsep = NULL;
351

    
352
        g_free(msg);
353
}
354

    
355
typedef enum janus_videoroom_audiocodec {
356
        JANUS_VIDEOROOM_OPUS,                /* Publishers will have to use OPUS         */
357
        JANUS_VIDEOROOM_ISAC_32K,        /* Publishers will have to use ISAC 32K */
358
        JANUS_VIDEOROOM_ISAC_16K,        /* Publishers will have to use ISAC 16K */
359
        JANUS_VIDEOROOM_PCMU,                /* Publishers will have to use PCMU 8K         */
360
        JANUS_VIDEOROOM_PCMA                /* Publishers will have to use PCMA 8K         */
361
} janus_videoroom_audiocodec;
362
static const char *janus_videoroom_audiocodec_name(janus_videoroom_audiocodec acodec) {
363
        switch(acodec) {
364
                case JANUS_VIDEOROOM_OPUS:
365
                        return "opus";
366
                case JANUS_VIDEOROOM_ISAC_32K:
367
                        return "isac32";
368
                case JANUS_VIDEOROOM_ISAC_16K:
369
                        return "isac16";
370
                case JANUS_VIDEOROOM_PCMU:
371
                        return "pcmu";
372
                case JANUS_VIDEOROOM_PCMA:
373
                        return "pcma";
374
                default:
375
                        /* Shouldn't happen */
376
                        return "opus";
377
        }
378
}
379

    
380
typedef enum janus_videoroom_videocodec {
381
        JANUS_VIDEOROOM_VP8,        /* Publishers will have to use VP8 */
382
        JANUS_VIDEOROOM_VP9,        /* Publishers will have to use VP9 */
383
        JANUS_VIDEOROOM_H264        /* Publishers will have to use H264 */
384
} janus_videoroom_videocodec;
385
static const char *janus_videoroom_videocodec_name(janus_videoroom_videocodec vcodec) {
386
        switch(vcodec) {
387
                case JANUS_VIDEOROOM_VP8:
388
                        return "vp8";
389
                case JANUS_VIDEOROOM_VP9:
390
                        return "vp9";
391
                case JANUS_VIDEOROOM_H264:
392
                        return "h264";
393
                default:
394
                        /* Shouldn't happen */
395
                        return "vp8";
396
        }
397
}
398

    
399
typedef struct janus_videoroom {
400
        guint64 room_id;                        /* Unique room ID */
401
        gchar *room_name;                        /* Room description */
402
        gchar *room_secret;                        /* Secret needed to manipulate (e.g., destroy) this room */
403
        gchar *room_pin;                        /* Password needed to join this room, if any */
404
        gboolean is_private;                /* Whether this room is 'private' (as in hidden) or not */
405
        int max_publishers;                        /* Maximum number of concurrent publishers */
406
        uint64_t bitrate;                        /* Global bitrate limit */
407
        uint16_t fir_freq;                        /* Regular FIR frequency (0=disabled) */
408
        janus_videoroom_audiocodec acodec;        /* Audio codec to force on publishers*/
409
        janus_videoroom_videocodec vcodec;        /* Video codec to force on publishers*/
410
        gboolean audiolevel_ext;        /* Whether the ssrc-audio-level extension must be negotiated or not for new publishers */
411
        gboolean videoorient_ext;        /* Whether the video-orientation extension must be negotiated or not for new publishers */
412
        gboolean playoutdelay_ext;        /* Whether the playout-delay extension must be negotiated or not for new publishers */
413
        gboolean record;                        /* Whether the feeds from publishers in this room should be recorded */
414
        char *rec_dir;                                /* Where to save the recordings of this room, if enabled */
415
        gint64 destroyed;                        /* Value to flag the room for destruction, done lazily */
416
        GHashTable *participants;        /* Map of potential publishers (we get listeners from them) */
417
        GHashTable *private_ids;        /* Map of existing private IDs */
418
        gboolean check_tokens;                /* Whether to check tokens when participants join (see below) */
419
        GHashTable *allowed;                /* Map of participants (as tokens) allowed to join */
420
        janus_mutex participants_mutex;/* Mutex to protect room properties */
421
} janus_videoroom;
422
static GHashTable *rooms;
423
static janus_mutex rooms_mutex;
424
static GList *old_rooms;
425
static char *admin_key = NULL;
426
static void janus_videoroom_free(janus_videoroom *room);
427

    
428
typedef struct janus_videoroom_session {
429
        janus_plugin_session *handle;
430
        janus_videoroom_p_type participant_type;
431
        gpointer participant;
432
        gboolean started;
433
        gboolean stopping;
434
        volatile gint hangingup;
435
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
436
} janus_videoroom_session;
437
static GHashTable *sessions;
438
static GList *old_sessions;
439
static janus_mutex sessions_mutex;
440

    
441
/* a host whose ports gets streamed rtp packets of the corresponding type. */
442
typedef struct janus_videoroom_rtp_forwarder {
443
        gboolean is_video;
444
        gboolean is_data;
445
        uint32_t ssrc;
446
        int payload_type;
447
        struct sockaddr_in serv_addr;
448
} janus_videoroom_rtp_forwarder;
449

    
450
typedef struct janus_videoroom_participant {
451
        janus_videoroom_session *session;
452
        janus_videoroom *room;        /* Room */
453
        guint64 user_id;        /* Unique ID in the room */
454
        guint32 pvt_id;                /* This is sent to the publisher for mapping purposes, but shouldn't be shared with others */
455
        gchar *display;                /* Display name (just for fun) */
456
        gchar *sdp;                        /* The SDP this publisher negotiated, if any */
457
        gboolean audio, video, data;                /* Whether audio, video and/or data is going to be sent by this publisher */
458
        guint32 audio_pt;                /* Audio payload type (Opus) */
459
        guint32 video_pt;                /* Video payload type (depends on room configuration) */
460
        guint32 audio_ssrc;                /* Audio SSRC of this publisher */
461
        guint32 video_ssrc;                /* Video SSRC of this publisher */
462
        guint8 audio_level_extmap_id;        /* Audio level extmap ID */
463
        guint8 video_orient_extmap_id;        /* Video orientation extmap ID */
464
        guint8 playout_delay_extmap_id;        /* Playout delay extmap ID */
465
        gboolean audio_active;
466
        gboolean video_active;
467
        int audio_active_packets; /* number of packets received with audio_levl extmap sdp header */
468
        int audio_dBov_sum;
469
        gboolean firefox;        /* We send Firefox users a different kind of FIR */
470
        uint64_t bitrate;
471
        gint64 remb_startup;/* Incremental changes on REMB to reach the target at startup */
472
        gint64 remb_latest;        /* Time of latest sent REMB (to avoid flooding) */
473
        gint64 fir_latest;        /* Time of latest sent FIR (to avoid flooding) */
474
        gint fir_seq;                /* FIR sequence number */
475
        gboolean recording_active;        /* Whether this publisher has to be recorded or not */
476
        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 */
477
        janus_recorder *arc;        /* The Janus recorder instance for this publisher's audio, if enabled */
478
        janus_recorder *vrc;        /* The Janus recorder instance for this publisher's video, if enabled */
479
        janus_recorder *drc;        /* The Janus recorder instance for this publisher's data, if enabled */
480
        janus_mutex rec_mutex;        /* Mutex to protect the recorders from race conditions */
481
        GSList *listeners;
482
        janus_mutex listeners_mutex;
483
        GHashTable *rtp_forwarders;
484
        janus_mutex rtp_forwarders_mutex;
485
        int udp_sock; /* The udp socket on which to forward rtp packets */
486
} janus_videoroom_participant;
487
static void janus_videoroom_participant_free(janus_videoroom_participant *p);
488
static void janus_videoroom_rtp_forwarder_free_helper(gpointer data);
489
static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_participant *p,
490
        const gchar* host, int port, int pt, uint32_t ssrc, gboolean is_video, gboolean is_data);
491
typedef struct janus_videoroom_listener_context {
492
        /* Needed to fix seq and ts in case of publisher switching */
493
        uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev,
494
                        v_last_ssrc, v_last_ts, v_base_ts, v_base_ts_prev;
495
        uint16_t a_last_seq, a_base_seq, a_base_seq_prev,
496
                        v_last_seq, v_base_seq, v_base_seq_prev;
497
        gboolean a_seq_reset, v_seq_reset;
498
} janus_videoroom_listener_context;
499

    
500
typedef struct janus_videoroom_listener {
501
        janus_videoroom_session *session;
502
        janus_videoroom *room;        /* Room */
503
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
504
        guint32 pvt_id;                /* Private ID of the participant that is subscribing (if available/provided) */
505
        janus_videoroom_listener_context context;        /* Needed in case there are publisher switches on this listener */
506
        gboolean audio, video, data;                /* Whether audio, video and/or data must be sent to this publisher */
507
        struct janus_videoroom_listener_muxed *parent;        /* Overall subscriber, if this is a sub-listener in a Multiplexed one */
508
        gboolean paused;
509
} janus_videoroom_listener;
510
static void janus_videoroom_listener_free(janus_videoroom_listener *l);
511

    
512
typedef struct janus_videoroom_listener_muxed {
513
        janus_videoroom_session *session;
514
        janus_videoroom *room;        /* Room */
515
        GSList *listeners;        /* List of listeners (as a Multiplexed listener can be subscribed to more publishers at the same time) */
516
        janus_mutex listeners_mutex;
517
} janus_videoroom_listener_muxed;
518
static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l);
519

    
520
typedef struct janus_videoroom_rtp_relay_packet {
521
        rtp_header *data;
522
        gint length;
523
        gboolean is_video;
524
        uint32_t timestamp;
525
        uint16_t seq_number;
526
} janus_videoroom_rtp_relay_packet;
527

    
528
/* SDP offer/answer templates */
529
#define OPUS_PT        111
530
#define ISAC32_PT        104
531
#define ISAC16_PT        103
532
#define PCMU_PT        0
533
#define PCMA_PT        8
534
#define VP8_PT                100
535
#define VP9_PT                101
536
#define H264_PT        107
537
#define sdp_template \
538
                "v=0\r\n" \
539
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */ \
540
                "s=%s\r\n"                                                        /* Video room name */ \
541
                "t=0 0\r\n" \
542
                "%s%s%s"                                /* Audio, video and/or data channel m-lines */
543
#define sdp_a_template_opus \
544
                "m=audio 1 RTP/SAVPF %d\r\n"                /* Opus payload type */ \
545
                "c=IN IP4 1.1.1.1\r\n" \
546
                "a=%s\r\n"                                                        /* Media direction */ \
547
                "a=rtpmap:%d opus/48000/2\r\n"                /* Opus payload type */ \
548
                "%s"                                                                /* extmap(s), if any */
549
#define sdp_a_template_isac32 \
550
                "m=audio 1 RTP/SAVPF %d\r\n"                /* ISAC32_PT payload type */ \
551
                "c=IN IP4 1.1.1.1\r\n" \
552
                "a=%s\r\n"                                                        /* Media direction */ \
553
                "a=rtpmap:%d ISAC/32000\r\n"                /* ISAC32_PT payload type */ \
554
                "%s"                                                                /* extmap(s), if any */
555
#define sdp_a_template_isac16 \
556
                "m=audio 1 RTP/SAVPF %d\r\n"                /* ISAC16_PT payload type */ \
557
                "c=IN IP4 1.1.1.1\r\n" \
558
                "a=%s\r\n"                                                        /* Media direction */ \
559
                "a=rtpmap:%d ISAC/16000\r\n"                /* ISAC16_PT payload type */ \
560
                "%s"                                                                /* extmap(s), if any */
561
#define sdp_a_template_pcmu \
562
                "m=audio 1 RTP/SAVPF %d\r\n"                /* PCMU_PT payload type */ \
563
                "c=IN IP4 1.1.1.1\r\n" \
564
                "a=%s\r\n"                                                        /* Media direction */ \
565
                "a=rtpmap:%d PCMU/8000\r\n"                    /* PCMU_PT payload type */ \
566
                "%s"                                                                /* extmap(s), if any */
567
#define sdp_a_template_pcma \
568
                "m=audio 1 RTP/SAVPF %d\r\n"                /* PCMA_PT payload type */ \
569
                "c=IN IP4 1.1.1.1\r\n" \
570
                "a=%s\r\n"                                                        /* Media direction */ \
571
                "a=rtpmap:%d PCMA/8000\r\n"                    /* PCMA_PT payload type */ \
572
                "%s"                                                                /* extmap(s), if any */
573
#define sdp_v_template_vp8 \
574
                "m=video 1 RTP/SAVPF %d\r\n"                /* VP8 payload type */ \
575
                "c=IN IP4 1.1.1.1\r\n" \
576
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
577
                "a=%s\r\n"                                                        /* Media direction */ \
578
                "a=rtpmap:%d VP8/90000\r\n"                        /* VP8 payload type */ \
579
                "a=rtcp-fb:%d ccm fir\r\n"                        /* VP8 payload type */ \
580
                "a=rtcp-fb:%d nack\r\n"                                /* VP8 payload type */ \
581
                "a=rtcp-fb:%d nack pli\r\n"                        /* VP8 payload type */ \
582
                "a=rtcp-fb:%d goog-remb\r\n"                /* VP8 payload type */ \
583
                "%s"                                                                /* extmap(s), if any */
584
#define sdp_v_template_vp9 \
585
                "m=video 1 RTP/SAVPF %d\r\n"                /* VP9 payload type */ \
586
                "c=IN IP4 1.1.1.1\r\n" \
587
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
588
                "a=%s\r\n"                                                        /* Media direction */ \
589
                "a=rtpmap:%d VP9/90000\r\n"                        /* VP9 payload type */ \
590
                "a=rtcp-fb:%d ccm fir\r\n"                        /* VP9 payload type */ \
591
                "a=rtcp-fb:%d nack\r\n"                                /* VP9 payload type */ \
592
                "a=rtcp-fb:%d nack pli\r\n"                        /* VP9 payload type */ \
593
                "a=rtcp-fb:%d goog-remb\r\n"                /* VP9 payload type */ \
594
                "%s"                                                                /* extmap(s), if any */
595
#define sdp_v_template_h264 \
596
                "m=video 1 RTP/SAVPF %d\r\n"                /* H264 payload type */ \
597
                "c=IN IP4 1.1.1.1\r\n" \
598
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
599
                "a=%s\r\n"                                                        /* Media direction */ \
600
                "a=rtpmap:%d H264/90000\r\n"                /* H264 payload type */ \
601
                "a=fmtp:%d profile-level-id=42e01f;packetization-mode=1\r\n" \
602
                "a=rtcp-fb:%d ccm fir\r\n"                        /* H264 payload type */ \
603
                "a=rtcp-fb:%d nack\r\n"                                /* H264 payload type */ \
604
                "a=rtcp-fb:%d nack pli\r\n"                        /* H264 payload type */ \
605
                "a=rtcp-fb:%d goog-remb\r\n"                /* H264 payload type */ \
606
                "%s"                                                                /* extmap(s), if any */
607
#ifdef HAVE_SCTP
608
#define sdp_d_template \
609
                "m=application 1 DTLS/SCTP 5000\r\n" \
610
                "c=IN IP4 1.1.1.1\r\n" \
611
                "a=sctpmap:5000 webrtc-datachannel 16\r\n"
612
#else
613
#define sdp_d_template \
614
                "m=application 0 DTLS/SCTP 0\r\n" \
615
                "c=IN IP4 1.1.1.1\r\n"
616
#endif
617

    
618

    
619
/* Error codes */
620
#define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR                499
621
#define JANUS_VIDEOROOM_ERROR_NO_MESSAGE                421
622
#define JANUS_VIDEOROOM_ERROR_INVALID_JSON                422
623
#define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST        423
624
#define JANUS_VIDEOROOM_ERROR_JOIN_FIRST                424
625
#define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED        425
626
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM                426
627
#define JANUS_VIDEOROOM_ERROR_ROOM_EXISTS                427
628
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED                428
629
#define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT        429
630
#define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT        430
631
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE        431
632
#define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL        432
633
#define JANUS_VIDEOROOM_ERROR_UNAUTHORIZED                433
634
#define JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED        434
635
#define JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED                435
636
#define JANUS_VIDEOROOM_ERROR_ID_EXISTS                        436
637
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP                437
638

    
639

    
640
/* Multiplexing helpers */
641
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
642
int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
643
int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, json_t *event);
644

    
645
static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_participant *p,
646
                const gchar* host, int port, int pt, uint32_t ssrc, gboolean is_video, gboolean is_data) {
647
        if(!p || !host) {
648
                return 0;
649
        }
650
        janus_videoroom_rtp_forwarder *forward = g_malloc0(sizeof(janus_videoroom_rtp_forwarder));
651
        forward->is_video = is_video;
652
        forward->payload_type = pt;
653
        forward->ssrc = ssrc;
654
        forward->is_data = is_data;
655
        forward->serv_addr.sin_family = AF_INET;
656
        inet_pton(AF_INET, host, &(forward->serv_addr.sin_addr));
657
        forward->serv_addr.sin_port = htons(port);
658
        janus_mutex_lock(&p->rtp_forwarders_mutex);
659
        guint32 stream_id = janus_random_uint32();
660
        while(g_hash_table_lookup(p->rtp_forwarders, GUINT_TO_POINTER(stream_id)) != NULL) {
661
                stream_id = janus_random_uint32();
662
        }
663
        g_hash_table_insert(p->rtp_forwarders, GUINT_TO_POINTER(stream_id), forward);
664
        janus_mutex_unlock(&p->rtp_forwarders_mutex);
665
        JANUS_LOG(LOG_VERB, "Added %s rtp_forward to participant %"SCNu64" host: %s:%d stream_id: %"SCNu32"\n",
666
                is_data ? "data" : (is_video ? "video" : "audio"), p->user_id, host, port, stream_id);
667
        return stream_id;
668
}
669

    
670

    
671
/* Convenience function for freeing a session */
672
static void session_free(gpointer data) {
673
        if(data) {
674
                janus_videoroom_session* session = (janus_videoroom_session*)data;
675
                switch(session->participant_type) {
676
                case janus_videoroom_p_type_publisher: 
677
                        janus_videoroom_participant_free(session->participant);
678
                        break;   
679
                case janus_videoroom_p_type_subscriber:
680
                        janus_videoroom_listener_free(session->participant);
681
                        break;
682
                case janus_videoroom_p_type_subscriber_muxed:
683
                        janus_videoroom_muxed_listener_free(session->participant);
684
                        break;
685
                default:
686
                        break;
687
                }
688
                session->handle = NULL;
689
                g_free(session);
690
                session = NULL;
691
        }
692
}
693

    
694
static void janus_videoroom_rtp_forwarder_free_helper(gpointer data) {
695
        if(data) {
696
                janus_videoroom_rtp_forwarder* forward = (janus_videoroom_rtp_forwarder*)data;
697
                if(forward) {
698
                        g_free(forward);
699
                        forward = NULL;
700
                }
701
        }
702
}
703

    
704
/* Convenience wrapper function for session_free that corresponds to GHRFunc() format for hash table cleanup */
705
static gboolean session_hash_table_remove(gpointer key, gpointer value, gpointer not_used) {
706
        if(value) {
707
                session_free(value);
708
        }
709
        return TRUE;
710
}
711

    
712
/* VideoRoom watchdog/garbage collector (sort of) */
713
void *janus_videoroom_watchdog(void *data);
714
void *janus_videoroom_watchdog(void *data) {
715
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog started\n");
716
        gint64 now = 0, room_now = 0;
717
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
718
                janus_mutex_lock(&sessions_mutex);
719
                /* Iterate on all the participants/listeners and check if we need to remove any of them */
720
                now = janus_get_monotonic_time();
721
                if(old_sessions != NULL) {
722
                        GList *sl = old_sessions;
723
                        JANUS_LOG(LOG_HUGE, "Checking %d old VideoRoom sessions...\n", g_list_length(old_sessions));
724
                        while(sl) {
725
                                janus_videoroom_session *session = (janus_videoroom_session *)sl->data;
726
                                /* If we are stopping, their is no point to continue to iterate */
727
                                if(!initialized || stopping) {
728
                                        break;
729
                                }
730
                                if(!session) {
731
                                        sl = sl->next;
732
                                        continue;
733
                                }
734
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
735
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
736
                                        JANUS_LOG(LOG_VERB, "Freeing old VideoRoom session\n");
737
                                        GList *rm = sl->next;
738
                                        old_sessions = g_list_delete_link(old_sessions, sl);
739
                                        sl = rm;
740
                                        g_hash_table_steal(sessions, session->handle);
741
                                        session_free(session);
742
                                        continue;
743
                                }
744
                                sl = sl->next;
745
                        }
746
                }
747
                janus_mutex_unlock(&sessions_mutex);
748
                janus_mutex_lock(&rooms_mutex);
749
                if(old_rooms != NULL) {
750
                        GList *rl = old_rooms;
751
                        room_now = janus_get_monotonic_time();
752
                        while(rl) {
753
                                janus_videoroom* room = (janus_videoroom*)rl->data;
754
                                if(!initialized || stopping){
755
                                        break;
756
                                }
757
                                if(!room) {
758
                                        rl = rl->next;
759
                                        continue;
760
                                }
761
                                if(room_now - room->destroyed >= 5*G_USEC_PER_SEC) {
762
                                        GList *rm = rl->next;
763
                                        old_rooms = g_list_delete_link(old_rooms, rl);
764
                                        rl = rm;
765
                                        g_hash_table_remove(rooms, &room->room_id);
766
                                        continue;
767
                                }
768
                                rl = rl->next;
769
                        }
770
                }
771
                janus_mutex_unlock(&rooms_mutex);
772
                g_usleep(500000);
773
        }
774
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog stopped\n");
775
        return NULL;
776
}
777

    
778

    
779
/* Plugin implementation */
780
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
781
        if(g_atomic_int_get(&stopping)) {
782
                /* Still stopping from before */
783
                return -1;
784
        }
785
        if(callback == NULL || config_path == NULL) {
786
                /* Invalid arguments */
787
                return -1;
788
        }
789

    
790
        /* Read configuration */
791
        char filename[255];
792
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
793
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
794
        config = janus_config_parse(filename);
795
        config_folder = config_path;
796
        if(config != NULL)
797
                janus_config_print(config);
798
        janus_mutex_init(&config_mutex);
799

    
800
        rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal,
801
                (GDestroyNotify)g_free, (GDestroyNotify) janus_videoroom_free);
802
        janus_mutex_init(&rooms_mutex);
803
        sessions = g_hash_table_new(NULL, NULL);
804
        janus_mutex_init(&sessions_mutex);
805

    
806
        messages = g_async_queue_new_full((GDestroyNotify) janus_videoroom_message_free);
807

    
808
        /* This is the callback we'll need to invoke to contact the gateway */
809
        gateway = callback;
810

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

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

    
957
        g_atomic_int_set(&initialized, 1);
958

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

    
980
void janus_videoroom_destroy(void) {
981
        if(!g_atomic_int_get(&initialized))
982
                return;
983
        g_atomic_int_set(&stopping, 1);
984

    
985
        g_async_queue_push(messages, &exit_message);
986
        if(handler_thread != NULL) {
987
                g_thread_join(handler_thread);
988
                handler_thread = NULL;
989
        }
990
        if(watchdog != NULL) {
991
                g_thread_join(watchdog);
992
                watchdog = NULL;
993
        }
994

    
995
        /* FIXME We should destroy the sessions cleanly */
996
        janus_mutex_lock(&sessions_mutex);
997
        g_hash_table_foreach_remove(sessions, (GHRFunc)session_hash_table_remove, NULL);
998
        g_hash_table_destroy(sessions);
999
        sessions = NULL;
1000
        janus_mutex_unlock(&sessions_mutex);
1001

    
1002
        janus_mutex_lock(&rooms_mutex);
1003
        g_hash_table_destroy(rooms);
1004
        rooms = NULL;
1005
        janus_mutex_unlock(&rooms_mutex);
1006
        janus_mutex_destroy(&rooms_mutex);
1007

    
1008
        g_async_queue_unref(messages);
1009
        messages = NULL;
1010

    
1011
        janus_config_destroy(config);
1012
        g_free(admin_key);
1013

    
1014
        g_atomic_int_set(&initialized, 0);
1015
        g_atomic_int_set(&stopping, 0);
1016
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
1017
}
1018

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

    
1024
int janus_videoroom_get_version(void) {
1025
        return JANUS_VIDEOROOM_VERSION;
1026
}
1027

    
1028
const char *janus_videoroom_get_version_string(void) {
1029
        return JANUS_VIDEOROOM_VERSION_STRING;
1030
}
1031

    
1032
const char *janus_videoroom_get_description(void) {
1033
        return JANUS_VIDEOROOM_DESCRIPTION;
1034
}
1035

    
1036
const char *janus_videoroom_get_name(void) {
1037
        return JANUS_VIDEOROOM_NAME;
1038
}
1039

    
1040
const char *janus_videoroom_get_author(void) {
1041
        return JANUS_VIDEOROOM_AUTHOR;
1042
}
1043

    
1044
const char *janus_videoroom_get_package(void) {
1045
        return JANUS_VIDEOROOM_PACKAGE;
1046
}
1047

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

    
1064
        return;
1065
}
1066

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

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

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

    
1144
        return;
1145
}
1146

    
1147
json_t *janus_videoroom_query_session(janus_plugin_session *handle) {
1148
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1149
                return NULL;
1150
        }        
1151
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1152
        if(!session) {
1153
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1154
                return NULL;
1155
        }
1156
        /* Show the participant/room info, if any */
1157
        json_t *info = json_object();
1158
        if(session->participant) {
1159
                if(session->participant_type == janus_videoroom_p_type_none) {
1160
                        json_object_set_new(info, "type", json_string("none"));
1161
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
1162
                        json_object_set_new(info, "type", json_string("publisher"));
1163
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1164
                        if(participant) {
1165
                                janus_videoroom *room = participant->room; 
1166
                                json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1167
                                json_object_set_new(info, "id", json_integer(participant->user_id));
1168
                                json_object_set_new(info, "private_id", json_integer(participant->pvt_id));
1169
                                if(participant->display)
1170
                                        json_object_set_new(info, "display", json_string(participant->display));
1171
                                if(participant->listeners)
1172
                                        json_object_set_new(info, "viewers", json_integer(g_slist_length(participant->listeners)));
1173
                                json_t *media = json_object();
1174
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1175
                                json_object_set_new(media, "video", json_integer(participant->video));
1176
                                json_object_set_new(media, "data", json_integer(participant->data));
1177
                                json_object_set_new(info, "media", media);
1178
                                json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
1179
                                if(participant->arc || participant->vrc || participant->drc) {
1180
                                        json_t *recording = json_object();
1181
                                        if(participant->arc && participant->arc->filename)
1182
                                                json_object_set_new(recording, "audio", json_string(participant->arc->filename));
1183
                                        if(participant->vrc && participant->vrc->filename)
1184
                                                json_object_set_new(recording, "video", json_string(participant->vrc->filename));
1185
                                        if(participant->drc && participant->drc->filename)
1186
                                                json_object_set_new(recording, "data", json_string(participant->drc->filename));
1187
                                        json_object_set_new(info, "recording", recording);
1188
                                }
1189
                        }
1190
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1191
                        json_object_set_new(info, "type", json_string("listener"));
1192
                        janus_videoroom_listener *participant = (janus_videoroom_listener *)session->participant;
1193
                        if(participant) {
1194
                                janus_videoroom_participant *feed = (janus_videoroom_participant *)participant->feed;
1195
                                if(feed) {
1196
                                        janus_videoroom *room = feed->room; 
1197
                                        json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1198
                                        json_object_set_new(info, "private_id", json_integer(participant->pvt_id));
1199
                                        json_object_set_new(info, "feed_id", json_integer(feed->user_id));
1200
                                        if(feed->display)
1201
                                                json_object_set_new(info, "feed_display", json_string(feed->display));
1202
                                }
1203
                                json_t *media = json_object();
1204
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1205
                                json_object_set_new(media, "video", json_integer(participant->video));
1206
                                json_object_set_new(media, "data", json_integer(participant->data));
1207
                                json_object_set_new(info, "media", media);
1208
                        }
1209
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1210
                        json_object_set_new(info, "type", json_string("muxed-listener"));
1211
                        /* TODO */
1212
                }
1213
        }
1214
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
1215
        return info;
1216
}
1217

    
1218
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) {
1219
        /* rooms_mutex has to be locked */
1220
        int error_code = 0;
1221
        json_t *room = json_object_get(root, "room");
1222
        guint64 room_id = json_integer_value(room);
1223
        *videoroom = g_hash_table_lookup(rooms, &room_id);
1224
        if(*videoroom == NULL) {
1225
                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1226
                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1227
                if(error_cause)
1228
                        g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
1229
                return error_code;
1230
        }
1231
        if((*videoroom)->destroyed) {
1232
                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1233
                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1234
                if(error_cause)
1235
                        g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
1236
                return error_code;
1237
        }
1238
        if(check_secret) {
1239
                char error_cause2[100];
1240
                JANUS_CHECK_SECRET((*videoroom)->room_secret, root, "secret", error_code, error_cause2,
1241
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1242
                if(error_code != 0) {
1243
                        g_strlcpy(error_cause, error_cause2, error_cause_size);
1244
                        return error_code;
1245
                }
1246
        }
1247
        if(check_pin) {
1248
                char error_cause2[100];
1249
                JANUS_CHECK_SECRET((*videoroom)->room_pin, root, "pin", error_code, error_cause2,
1250
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1251
                if(error_code != 0) {
1252
                        g_strlcpy(error_cause, error_cause2, error_cause_size);
1253
                        return error_code;
1254
                }
1255
        }
1256
        return 0;
1257
}
1258

    
1259
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
1260
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1261
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
1262
        
1263
        /* Pre-parse the message */
1264
        int error_code = 0;
1265
        char error_cause[512];
1266
        json_t *root = message;
1267
        json_t *response = NULL;
1268

    
1269
        if(message == NULL) {
1270
                JANUS_LOG(LOG_ERR, "No message??\n");
1271
                error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
1272
                g_snprintf(error_cause, 512, "%s", "No message??");
1273
                goto plugin_response;
1274
        }
1275

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

    
1806
                guint64 room_id = json_integer_value(room);
1807
                guint64 publisher_id = json_integer_value(pub_id);
1808
                guint32 stream_id = json_integer_value(id);
1809
                janus_mutex_lock(&rooms_mutex);
1810
                janus_videoroom *videoroom = NULL;
1811
                error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
1812
                janus_mutex_unlock(&rooms_mutex);
1813
                if(error_code != 0)
1814
                        goto plugin_response;
1815
                janus_mutex_lock(&videoroom->participants_mutex);
1816
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &publisher_id);
1817
                if(publisher == NULL) {
1818
                        janus_mutex_unlock(&videoroom->participants_mutex);
1819
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1820
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1821
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1822
                        goto plugin_response;
1823
                }
1824
                janus_mutex_lock(&publisher->rtp_forwarders_mutex);
1825
                if(g_hash_table_lookup(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id)) == NULL) {
1826
                        janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1827
                        janus_mutex_unlock(&videoroom->participants_mutex);
1828
                        JANUS_LOG(LOG_ERR, "No such stream (%"SCNu32")\n", stream_id);
1829
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1830
                        g_snprintf(error_cause, 512, "No such stream (%"SCNu32")", stream_id);
1831
                        goto plugin_response;
1832
                }
1833
                g_hash_table_remove(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id));
1834
                janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1835
                janus_mutex_unlock(&videoroom->participants_mutex);
1836
                response = json_object();
1837
                json_object_set_new(response, "videoroom", json_string("stop_rtp_forward"));
1838
                json_object_set_new(response, "room", json_integer(room_id));
1839
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1840
                json_object_set_new(response, "stream_id", json_integer(stream_id));
1841
                goto plugin_response;
1842
        } else if(!strcasecmp(request_text, "exists")) {
1843
                /* Check whether a given room exists or not, returns true/false */        
1844
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1845
                        error_code, error_cause, TRUE,
1846
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1847
                if(error_code != 0)
1848
                        goto plugin_response;
1849
                json_t *room = json_object_get(root, "room");
1850
                guint64 room_id = json_integer_value(room);
1851
                janus_mutex_lock(&rooms_mutex);
1852
                gboolean room_exists = g_hash_table_contains(rooms, &room_id);
1853
                janus_mutex_unlock(&rooms_mutex);
1854
                response = json_object();
1855
                json_object_set_new(response, "videoroom", json_string("success"));
1856
                json_object_set_new(response, "room", json_integer(room_id));
1857
                json_object_set_new(response, "exists", room_exists ? json_true() : json_false());
1858
                goto plugin_response;
1859
        } else if(!strcasecmp(request_text, "allowed")) {
1860
                JANUS_LOG(LOG_VERB, "Attempt to edit the list of allowed participants in an existing videoroom room\n");
1861
                JANUS_VALIDATE_JSON_OBJECT(root, allowed_parameters,
1862
                        error_code, error_cause, TRUE,
1863
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1864
                if(error_code != 0)
1865
                        goto plugin_response;
1866
                json_t *action = json_object_get(root, "action");
1867
                json_t *room = json_object_get(root, "room");
1868
                json_t *allowed = json_object_get(root, "allowed");
1869
                const char *action_text = json_string_value(action);
1870
                if(strcasecmp(action_text, "enable") && strcasecmp(action_text, "disable") &&
1871
                                strcasecmp(action_text, "add") && strcasecmp(action_text, "remove")) {
1872
                        JANUS_LOG(LOG_ERR, "Unsupported action '%s' (allowed)\n", action_text);
1873
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1874
                        g_snprintf(error_cause, 512, "Unsupported action '%s' (allowed)", action_text);
1875
                        goto plugin_response;
1876
                }
1877
                guint64 room_id = json_integer_value(room);
1878
                janus_mutex_lock(&rooms_mutex);
1879
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
1880
                if(videoroom == NULL) {
1881
                        janus_mutex_unlock(&rooms_mutex);
1882
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1883
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1884
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1885
                        goto plugin_response;
1886
                }
1887
                /* A secret may be required for this action */
1888
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1889
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1890
                if(error_code != 0) {
1891
                        janus_mutex_unlock(&rooms_mutex);
1892
                        goto plugin_response;
1893
                }
1894
                if(!strcasecmp(action_text, "enable")) {
1895
                        JANUS_LOG(LOG_VERB, "Enabling the check on allowed authorization tokens for room %"SCNu64"\n", room_id);
1896
                        videoroom->check_tokens = TRUE;
1897
                } else if(!strcasecmp(action_text, "disable")) {
1898
                        JANUS_LOG(LOG_VERB, "Disabling the check on allowed authorization tokens for room %"SCNu64" (free entry)\n", room_id);
1899
                        videoroom->check_tokens = FALSE;
1900
                } else {
1901
                        gboolean add = !strcasecmp(action_text, "add");
1902
                        if(allowed) {
1903
                                /* Make sure the "allowed" array only contains strings */
1904
                                gboolean ok = TRUE;
1905
                                if(json_array_size(allowed) > 0) {
1906
                                        size_t i = 0;
1907
                                        for(i=0; i<json_array_size(allowed); i++) {
1908
                                                json_t *a = json_array_get(allowed, i);
1909
                                                if(!a || !json_is_string(a)) {
1910
                                                        ok = FALSE;
1911
                                                        break;
1912
                                                }
1913
                                        }
1914
                                }
1915
                                if(!ok) {
1916
                                        JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
1917
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1918
                                        g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
1919
                                        janus_mutex_unlock(&rooms_mutex);
1920
                                        goto plugin_response;
1921
                                }
1922
                                size_t i = 0;
1923
                                for(i=0; i<json_array_size(allowed); i++) {
1924
                                        const char *token = json_string_value(json_array_get(allowed, i));
1925
                                        if(add) {
1926
                                                if(!g_hash_table_lookup(videoroom->allowed, token))
1927
                                                        g_hash_table_insert(videoroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
1928
                                        } else {
1929
                                                g_hash_table_remove(videoroom->allowed, token);
1930
                                        }
1931
                                }
1932
                        }
1933
                }
1934
                /* Prepare response */
1935
                response = json_object();
1936
                json_object_set_new(response, "videoroom", json_string("success"));
1937
                json_object_set_new(response, "room", json_integer(videoroom->room_id));
1938
                json_t *list = json_array();
1939
                if(strcasecmp(action_text, "disable")) {
1940
                        if(g_hash_table_size(videoroom->allowed) > 0) {
1941
                                GHashTableIter iter;
1942
                                gpointer key;
1943
                                g_hash_table_iter_init(&iter, videoroom->allowed);
1944
                                while(g_hash_table_iter_next(&iter, &key, NULL)) {
1945
                                        char *token = key;
1946
                                        json_array_append_new(list, json_string(token));
1947
                                }
1948
                        }
1949
                        json_object_set_new(response, "allowed", list);
1950
                }
1951
                /* Done */
1952
                janus_mutex_unlock(&rooms_mutex);
1953
                JANUS_LOG(LOG_VERB, "VideoRoom room allowed list updated\n");
1954
                goto plugin_response;
1955
        } else if(!strcasecmp(request_text, "kick")) {
1956
                JANUS_LOG(LOG_VERB, "Attempt to kick a participant from an existing videoroom room\n");
1957
                JANUS_VALIDATE_JSON_OBJECT(root, kick_parameters,
1958
                        error_code, error_cause, TRUE,
1959
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1960
                if(error_code != 0)
1961
                        goto plugin_response;
1962
                json_t *room = json_object_get(root, "room");
1963
                json_t *id = json_object_get(root, "id");
1964
                guint64 room_id = json_integer_value(room);
1965
                janus_mutex_lock(&rooms_mutex);
1966
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
1967
                if(videoroom == NULL) {
1968
                        janus_mutex_unlock(&rooms_mutex);
1969
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1970
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1971
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1972
                        goto plugin_response;
1973
                }
1974
                janus_mutex_lock(&videoroom->participants_mutex);
1975
                /* A secret may be required for this action */
1976
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1977
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1978
                if(error_code != 0) {
1979
                        janus_mutex_unlock(&videoroom->participants_mutex);
1980
                        janus_mutex_unlock(&rooms_mutex);
1981
                        goto plugin_response;
1982
                }
1983
                guint64 user_id = json_integer_value(id);
1984
                janus_videoroom_participant *participant = g_hash_table_lookup(videoroom->participants, &user_id);
1985
                if(participant == NULL) {
1986
                        janus_mutex_unlock(&videoroom->participants_mutex);
1987
                        janus_mutex_unlock(&rooms_mutex);
1988
                        JANUS_LOG(LOG_ERR, "No such user %"SCNu64" in room %"SCNu64"\n", user_id, room_id);
1989
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1990
                        g_snprintf(error_cause, 512, "No such user %"SCNu64" in room %"SCNu64, user_id, room_id);
1991
                        goto plugin_response;
1992
                }
1993
                /* Tell the core to tear down the PeerConnection, hangup_media will do the rest
1994
                 *         FIXME: this only kicks the publisher, but not the subscriptions it created */
1995
                if(participant && participant->session)
1996
                        gateway->close_pc(participant->session->handle);
1997
                JANUS_LOG(LOG_VERB, "Kicked user %"SCNu64" from room %"SCNu64"\n", user_id, room_id);
1998
                /* Prepare response */
1999
                response = json_object();
2000
                json_object_set_new(response, "videoroom", json_string("success"));
2001
                /* Done */
2002
                janus_mutex_unlock(&videoroom->participants_mutex);
2003
                janus_mutex_unlock(&rooms_mutex);
2004
                goto plugin_response;
2005
        } else if(!strcasecmp(request_text, "listparticipants")) {
2006
                /* List all participants in a room, specifying whether they're publishers or just attendees */        
2007
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2008
                        error_code, error_cause, TRUE,
2009
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2010
                if(error_code != 0)
2011
                        goto plugin_response;
2012
                json_t *room = json_object_get(root, "room");
2013
                guint64 room_id = json_integer_value(room);
2014
                janus_mutex_lock(&rooms_mutex);
2015
                janus_videoroom *videoroom = NULL;
2016
                error_code = janus_videoroom_access_room(root, FALSE, FALSE, &videoroom, error_cause, sizeof(error_cause));
2017
                janus_mutex_unlock(&rooms_mutex);
2018
                if(error_code != 0)
2019
                        goto plugin_response;
2020
                /* Return a list of all participants (whether they're publishing or not) */
2021
                json_t *list = json_array();
2022
                GHashTableIter iter;
2023
                gpointer value;
2024
                janus_mutex_lock(&videoroom->participants_mutex);
2025
                g_hash_table_iter_init(&iter, videoroom->participants);
2026
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2027
                        janus_videoroom_participant *p = value;
2028
                        json_t *pl = json_object();
2029
                        json_object_set_new(pl, "id", json_integer(p->user_id));
2030
                        if(p->display)
2031
                                json_object_set_new(pl, "display", json_string(p->display));
2032
                        json_object_set_new(pl, "publisher", (p->sdp && p->session->started) ? json_true() : json_false());
2033
                        if ((p->sdp && p->session->started)) {
2034
                                json_object_set_new(pl, "internal_audio_ssrc", json_integer(p->audio_ssrc));
2035
                                json_object_set_new(pl, "internal_video_ssrc", json_integer(p->video_ssrc));
2036
                        }
2037
                        json_array_append_new(list, pl);
2038
                }
2039
                janus_mutex_unlock(&videoroom->participants_mutex);
2040
                response = json_object();
2041
                json_object_set_new(response, "videoroom", json_string("participants"));
2042
                json_object_set_new(response, "room", json_integer(room_id));
2043
                json_object_set_new(response, "participants", list);
2044
                goto plugin_response;
2045
        } else if(!strcasecmp(request_text, "listforwarders")) {
2046
                /* List all forwarders in a room */        
2047
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2048
                        error_code, error_cause, TRUE,
2049
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2050
                if(error_code != 0)
2051
                        goto plugin_response;
2052
                json_t *room = json_object_get(root, "room");
2053
                guint64 room_id = json_integer_value(room);
2054
                janus_mutex_lock(&rooms_mutex);
2055
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
2056
                if(videoroom == NULL) {
2057
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2058
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2059
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2060
                        janus_mutex_unlock(&rooms_mutex);
2061
                        goto plugin_response;
2062
                }
2063
                if(videoroom->destroyed) {
2064
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2065
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2066
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2067
                        janus_mutex_unlock(&rooms_mutex);
2068
                        goto plugin_response;
2069
                }
2070
                /* A secret may be required for this action */
2071
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
2072
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2073
                if(error_code != 0) {
2074
                        janus_mutex_unlock(&rooms_mutex);
2075
                        goto plugin_response;
2076
                }
2077
                /* Return a list of all forwarders */
2078
                json_t *list = json_array();
2079
                GHashTableIter iter;
2080
                gpointer value;
2081
                janus_mutex_lock(&videoroom->participants_mutex);
2082
                g_hash_table_iter_init(&iter, videoroom->participants);
2083
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2084
                        janus_videoroom_participant *p = value;
2085
                        janus_mutex_lock(&p->rtp_forwarders_mutex);
2086
                        if(g_hash_table_size(p->rtp_forwarders) == 0) {
2087
                                janus_mutex_unlock(&p->rtp_forwarders_mutex);
2088
                                continue;
2089
                        }
2090
                        json_t *pl = json_object();
2091
                        json_object_set_new(pl, "publisher_id", json_integer(p->user_id));
2092
                        if(p->display)
2093
                                json_object_set_new(pl, "display", json_string(p->display));
2094
                        json_t *flist = json_array();
2095
                        GHashTableIter iter_f;
2096
                        gpointer key_f, value_f;                        
2097
                        g_hash_table_iter_init(&iter_f, p->rtp_forwarders);
2098
                        while(g_hash_table_iter_next(&iter_f, &key_f, &value_f)) {                                
2099
                                json_t *fl = json_object();
2100
                                guint32 rpk = GPOINTER_TO_UINT(key_f);
2101
                                janus_videoroom_rtp_forwarder *rpv = value_f;
2102
                                json_object_set_new(fl, "ip", json_string(inet_ntoa(rpv->serv_addr.sin_addr)));
2103
                                if(rpv->is_data) {
2104
                                        json_object_set_new(fl, "data_stream_id", json_integer(rpk));
2105
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
2106
                                } else if(rpv->is_video) {
2107
                                        json_object_set_new(fl, "video_stream_id", json_integer(rpk));
2108
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
2109
                                        if(rpv->payload_type)
2110
                                                json_object_set_new(fl, "pt", json_integer(rpv->payload_type));
2111
                                        if(rpv->ssrc)
2112
                                                json_object_set_new(fl, "ssrc", json_integer(rpv->ssrc));
2113
                                } else {
2114
                                        json_object_set_new(fl, "audio_stream_id", json_integer(rpk));
2115
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
2116
                                        if(rpv->payload_type)
2117
                                                json_object_set_new(fl, "pt", json_integer(rpv->payload_type));
2118
                                        if(rpv->ssrc)
2119
                                                json_object_set_new(fl, "ssrc", json_integer(rpv->ssrc));
2120
                                }
2121
                                json_array_append_new(flist, fl);
2122
                        }                
2123
                        janus_mutex_unlock(&p->rtp_forwarders_mutex);
2124
                        json_object_set_new(pl, "rtp_forwarder", flist);
2125
                        json_array_append_new(list, pl);
2126
                }
2127
                janus_mutex_unlock(&videoroom->participants_mutex);
2128
                janus_mutex_unlock(&rooms_mutex);
2129
                response = json_object();
2130
                json_object_set_new(response, "room", json_integer(room_id));
2131
                json_object_set_new(response, "rtp_forwarders", list);
2132
                goto plugin_response;
2133
        } else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")
2134
                        || !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish") || !strcasecmp(request_text, "unpublish")
2135
                        || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "switch") || !strcasecmp(request_text, "stop")
2136
                        || !strcasecmp(request_text, "add") || !strcasecmp(request_text, "remove") || !strcasecmp(request_text, "leave")) {
2137
                /* These messages are handled asynchronously */
2138

    
2139
                janus_videoroom_message *msg = g_malloc0(sizeof(janus_videoroom_message));
2140
                msg->handle = handle;
2141
                msg->transaction = transaction;
2142
                msg->message = root;
2143
                msg->jsep = jsep;
2144
                g_async_queue_push(messages, msg);
2145

    
2146
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
2147
        } else {
2148
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
2149
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
2150
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
2151
        }
2152

    
2153
plugin_response:
2154
                {
2155
                        if(error_code == 0 && !response) {
2156
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2157
                                g_snprintf(error_cause, 512, "Invalid response");
2158
                        }
2159
                        if(error_code != 0) {
2160
                                /* Prepare JSON error event */
2161
                                json_t *event = json_object();
2162
                                json_object_set_new(event, "videoroom", json_string("event"));
2163
                                json_object_set_new(event, "error_code", json_integer(error_code));
2164
                                json_object_set_new(event, "error", json_string(error_cause));
2165
                                response = event;
2166
                        }
2167
                        if(root != NULL)
2168
                                json_decref(root);
2169
                        if(jsep != NULL)
2170
                                json_decref(jsep);
2171
                        g_free(transaction);
2172

    
2173
                        return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
2174
                }
2175

    
2176
}
2177

    
2178
void janus_videoroom_setup_media(janus_plugin_session *handle) {
2179
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
2180
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2181
                return;
2182
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2183
        if(!session) {
2184
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2185
                return;
2186
        }
2187
        if(session->destroyed)
2188
                return;
2189
        g_atomic_int_set(&session->hangingup, 0);
2190

    
2191
        /* Media relaying can start now */
2192
        session->started = TRUE;
2193

    
2194
        if(session->participant) {
2195
                /* If this is a publisher, notify all listeners about the fact they can
2196
                 * now subscribe; if this is a listener, instead, ask the publisher a FIR */
2197
                if(session->participant_type == janus_videoroom_p_type_publisher) {
2198
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2199
                        /* Notify all other participants that there's a new boy in town */
2200
                        json_t *list = json_array();
2201
                        json_t *pl = json_object();
2202
                        json_object_set_new(pl, "id", json_integer(participant->user_id));
2203
                        if(participant->display)
2204
                                json_object_set_new(pl, "display", json_string(participant->display));
2205
                        json_array_append_new(list, pl);
2206
                        json_t *pub = json_object();
2207
                        json_object_set_new(pub, "videoroom", json_string("event"));
2208
                        json_object_set_new(pub, "room", json_integer(participant->room->room_id));
2209
                        json_object_set_new(pub, "publishers", list);
2210
                        GHashTableIter iter;
2211
                        gpointer value;
2212
                        janus_videoroom *videoroom = participant->room;
2213
                        janus_mutex_lock(&videoroom->participants_mutex);
2214
                        g_hash_table_iter_init(&iter, videoroom->participants);
2215
                        while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2216
                                janus_videoroom_participant *p = value;
2217
                                if(p == participant) {
2218
                                        continue;        /* Skip the new publisher itself */
2219
                                }
2220
                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2221
                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub, NULL);
2222
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
2223
                        }
2224
                        json_decref(pub);
2225
                        janus_mutex_unlock(&videoroom->participants_mutex);
2226
                        /* Also notify event handlers */
2227
                        if(notify_events && gateway->events_is_enabled()) {
2228
                                json_t *info = json_object();
2229
                                json_object_set_new(info, "event", json_string("published"));
2230
                                json_object_set_new(info, "room", json_integer(participant->room->room_id));
2231
                                json_object_set_new(info, "id", json_integer(participant->user_id));
2232
                                gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2233
                        }
2234
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2235
                        janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
2236
                        if(l && l->feed) {
2237
                                janus_videoroom_participant *p = l->feed;
2238
                                if(p && p->session) {
2239
                                        /* Send a FIR */
2240
                                        char buf[20];
2241
                                        memset(buf, 0, 20);
2242
                                        janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
2243
                                        JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2244
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 20);
2245
                                        /* Send a PLI too, just in case... */
2246
                                        memset(buf, 0, 12);
2247
                                        janus_rtcp_pli((char *)&buf, 12);
2248
                                        JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2249
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 12);
2250
                                        /* Also notify event handlers */
2251
                                        if(notify_events && gateway->events_is_enabled()) {
2252
                                                json_t *info = json_object();
2253
                                                json_object_set_new(info, "event", json_string("subscribed"));
2254
                                                json_object_set_new(info, "room", json_integer(p->room->room_id));
2255
                                                json_object_set_new(info, "feed", json_integer(p->user_id));
2256
                                                gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2257
                                        }
2258
                                }
2259
                        }
2260
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2261
                        /* Do the same, but for all feeds */
2262
                        janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
2263
                        if(listener == NULL)
2264
                                return;
2265
                        GSList *ps = listener->listeners;
2266
                        while(ps) {
2267
                                janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
2268
                                if(l && l->feed) {
2269
                                        janus_videoroom_participant *p = l->feed;
2270
                                        if(p && p->session) {
2271
                                                /* Send a FIR */
2272
                                                char buf[20];
2273
                                                memset(buf, 0, 20);
2274
                                                janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
2275
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2276
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
2277
                                                /* Send a PLI too, just in case... */
2278
                                                memset(buf, 0, 12);
2279
                                                janus_rtcp_pli((char *)&buf, 12);
2280
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2281
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 12);
2282
                                        }
2283
                                }
2284
                                ps = ps->next;
2285
                        }
2286
                }
2287
        }
2288
}
2289

    
2290
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
2291
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2292
                return;
2293
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2294
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
2295
                return;
2296
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2297
        if(participant->audio_level_extmap_id > 0) {
2298
            int level = 0;
2299
            int audio_level = janus_rtp_header_extension_parse_audio_level_and_return(buf, len, participant->audio_level_extmap_id, &level);
2300
            participant->audio_dBov_sum = participant->audio_dBov_sum + audio_level;
2301
            participant->audio_active_packets = participant->audio_active_packets + 1;
2302
                if(participant->audio_active_packets == 60) {
2303
                    if(participant->audio_dBov_sum < 1200) {
2304
                       float avg = (float)participant->audio_dBov_sum / participant->audio_active_packets;
2305
                       if(notify_events && gateway->events_is_enabled()) {
2306
                          json_t *info = json_object();
2307
                      json_object_set_new(info, "event", json_string("talking"));
2308
                      json_object_set_new(info, "room", json_string(participant->room));
2309
                      json_object_set_new(info, "user", json_integer(participant->user_id));
2310
                      gateway->notify_event(&janus_videoroom_plugin, handle, info);
2311
                      json_decref(info);
2312
                   }
2313
                       JANUS_LOG(LOG_ERR, "Got audio_active_packets of 30 pkts from user: %" G_GUINT64_FORMAT" with average audio_dBov of: %f \n", participant->user_id, avg);
2314
                    }
2315
                participant->audio_active_packets = 0;
2316
                participant->audio_dBov_sum = 0;
2317
                }
2318
    }
2319
        if((!video && participant->audio_active) || (video && participant->video_active)) {
2320
                /* Update payload type and SSRC */
2321
                janus_mutex_lock(&participant->rtp_forwarders_mutex);
2322
                rtp_header *rtp = (rtp_header *)buf;
2323
                rtp->type = video ? participant->video_pt : participant->audio_pt;
2324
                rtp->ssrc = htonl(video ? participant->video_ssrc : participant->audio_ssrc);
2325
                /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
2326
                GHashTableIter iter;
2327
                gpointer value;
2328
                g_hash_table_iter_init(&iter, participant->rtp_forwarders);
2329
                while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
2330
                        janus_videoroom_rtp_forwarder* rtp_forward = (janus_videoroom_rtp_forwarder*)value;
2331
                        /* Check if payload type and/or SSRC need to be overwritten for this forwarder */
2332
                        int pt = rtp->type;
2333
                        uint32_t ssrc = ntohl(rtp->ssrc);
2334
                        if(rtp_forward->payload_type > 0)
2335
                                rtp->type = rtp_forward->payload_type;
2336
                        if(rtp_forward->ssrc > 0)
2337
                                rtp->ssrc = htonl(rtp_forward->ssrc);
2338
                        if(video && rtp_forward->is_video) {
2339
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
2340
                        }
2341
                        else if(!video && !rtp_forward->is_video && !rtp_forward->is_data) {
2342
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
2343
                        }
2344
                        /* Restore original values of payload type and SSRC before going on */
2345
                        rtp->type = pt;
2346
                        rtp->ssrc = htonl(ssrc);
2347
                }
2348
                janus_mutex_unlock(&participant->rtp_forwarders_mutex);
2349
                /* Save the frame if we're recording */
2350
                janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len);
2351
                /* Done, relay it */
2352
                janus_videoroom_rtp_relay_packet packet;
2353
                packet.data = rtp;
2354
                packet.length = len;
2355
                packet.is_video = video;
2356
                /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */
2357
                packet.timestamp = ntohl(packet.data->timestamp);
2358
                packet.seq_number = ntohs(packet.data->seq_number);
2359
                /* Go */
2360
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
2361
                
2362
                /* Check if we need to send any REMB, FIR or PLI back to this publisher */
2363
                if(video && participant->video_active) {
2364
                        /* Did we send a REMB already, or is it time to send one? */
2365
                        gboolean send_remb = FALSE;
2366
                        if(participant->remb_latest == 0 && participant->remb_startup > 0) {
2367
                                /* Still in the starting phase, send the ramp-up REMB feedback */
2368
                                send_remb = TRUE;
2369
                        } else if(participant->remb_latest > 0 && janus_get_monotonic_time()-participant->remb_latest >= 5*G_USEC_PER_SEC) {
2370
                                /* 5 seconds have passed since the last REMB, send a new one */
2371
                                send_remb = TRUE;
2372
                        }                
2373
                        if(send_remb) {
2374
                                /* We send a few incremental REMB messages at startup */
2375
                                uint64_t bitrate = (participant->bitrate ? participant->bitrate : 256*1024);
2376
                                if(participant->remb_startup > 0) {
2377
                                        bitrate = bitrate/participant->remb_startup;
2378
                                        participant->remb_startup--;
2379
                                }
2380
                                JANUS_LOG(LOG_VERB, "Sending REMB (%s, %"SCNu64")\n", participant->display, bitrate);
2381
                                char rtcpbuf[24];
2382
                                janus_rtcp_remb((char *)(&rtcpbuf), 24, bitrate);
2383
                                gateway->relay_rtcp(handle, video, rtcpbuf, 24);
2384
                                if(participant->remb_startup == 0)
2385
                                        participant->remb_latest = janus_get_monotonic_time();
2386
                        }
2387
                        /* Generate FIR/PLI too, if needed */
2388
                        if(video && participant->video_active && (participant->room->fir_freq > 0)) {
2389
                                /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
2390
                                gint64 now = janus_get_monotonic_time();
2391
                                if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
2392
                                        /* FIXME We send a FIR every tot seconds */
2393
                                        participant->fir_latest = now;
2394
                                        char rtcpbuf[24];
2395
                                        memset(rtcpbuf, 0, 24);
2396
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &participant->fir_seq);
2397
                                        JANUS_LOG(LOG_VERB, "Sending FIR to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
2398
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 20);
2399
                                        /* Send a PLI too, just in case... */
2400
                                        memset(rtcpbuf, 0, 12);
2401
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2402
                                        JANUS_LOG(LOG_VERB, "Sending PLI to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
2403
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 12);
2404
                                }
2405
                        }
2406
                }
2407
        }
2408
}
2409

    
2410
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
2411
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2412
                return;
2413
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2414
        if(!session) {
2415
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2416
                return;
2417
        }
2418
        if(session->destroyed)
2419
                return;
2420
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
2421
                /* A listener sent some RTCP, check what it is and if we need to forward it to the publisher */
2422
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
2423
                if(!l || !l->video)
2424
                        return;        /* The only feedback we handle is video related anyway... */
2425
                if(janus_rtcp_has_fir(buf, len)) {
2426
                        /* We got a FIR, forward it to the publisher */
2427
                        if(l->feed) {
2428
                                janus_videoroom_participant *p = l->feed;
2429
                                if(p && p->session) {
2430
                                        char rtcpbuf[20];
2431
                                        memset(rtcpbuf, 0, 20);
2432
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &p->fir_seq);
2433
                                        JANUS_LOG(LOG_VERB, "Got a FIR from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2434
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 20);
2435
                                }
2436
                        }
2437
                }
2438
                if(janus_rtcp_has_pli(buf, len)) {
2439
                        /* We got a PLI, forward it to the publisher */
2440
                        if(l->feed) {
2441
                                janus_videoroom_participant *p = l->feed;
2442
                                if(p && p->session) {
2443
                                        char rtcpbuf[12];
2444
                                        memset(rtcpbuf, 0, 12);
2445
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2446
                                        JANUS_LOG(LOG_VERB, "Got a PLI from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2447
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 12);
2448
                                }
2449
                        }
2450
                }
2451
                uint64_t bitrate = janus_rtcp_get_remb(buf, len);
2452
                if(bitrate > 0) {
2453
                        /* FIXME We got a REMB from this listener, should we do something about it? */
2454
                }
2455
        }
2456
}
2457

    
2458
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
2459
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2460
                return;
2461
        if(buf == NULL || len <= 0)
2462
                return;
2463
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2464
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
2465
                return;
2466
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2467
        /* Any forwarder involved? */
2468
        janus_mutex_lock(&participant->rtp_forwarders_mutex);
2469
        /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
2470
        GHashTableIter iter;
2471
        gpointer value;
2472
        g_hash_table_iter_init(&iter, participant->rtp_forwarders);
2473
        while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
2474
                janus_videoroom_rtp_forwarder* rtp_forward = (janus_videoroom_rtp_forwarder*)value;
2475
                if(rtp_forward->is_data) {
2476
                        sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
2477
                }
2478
        }
2479
        janus_mutex_unlock(&participant->rtp_forwarders_mutex);
2480
        /* Get a string out of the data */
2481
        char *text = g_malloc0(len+1);
2482
        memcpy(text, buf, len);
2483
        *(text+len) = '\0';
2484
        JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
2485
        /* Save the message if we're recording */
2486
        janus_recorder_save_frame(participant->drc, text, strlen(text));
2487
        /* Relay to all listeners */
2488
        g_slist_foreach(participant->listeners, janus_videoroom_relay_data_packet, text);
2489
        g_free(text);
2490
}
2491

    
2492
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
2493
        /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */
2494
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2495
                return;
2496
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2497
        if(!session || session->destroyed || !session->participant)
2498
                return;
2499
        /* Check if it's an uplink (publisher) or downlink (viewer) issue */
2500
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2501
                if(!uplink) {
2502
                        janus_videoroom_participant *publisher = (janus_videoroom_participant *)session->participant;
2503
                        if(publisher) {
2504
                                /* Send an event on the handle to notify the application: it's
2505
                                 * up to the application to then choose a policy and enforce it */
2506
                                json_t *event = json_object();
2507
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2508
                                /* Also add info on what the current bitrate cap is */
2509
                                uint64_t bitrate = (publisher->bitrate ? publisher->bitrate : 256*1024);
2510
                                json_object_set_new(event, "current-bitrate", json_integer(bitrate));
2511
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
2512
                                json_decref(event);
2513
                        }
2514
                } else {
2515
                        JANUS_LOG(LOG_WARN, "Got a slow uplink on a VideoRoom publisher? Weird, because it doesn't receive media...\n");
2516
                }
2517
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2518
                if(uplink) {
2519
                        janus_videoroom_listener *viewer = (janus_videoroom_listener *)session->participant;
2520
                        if(viewer) {
2521
                                /* Send an event on the handle to notify the application: it's
2522
                                 * up to the application to then choose a policy and enforce it */
2523
                                json_t *event = json_object();
2524
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2525
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
2526
                                json_decref(event);
2527
                        }
2528
                } else {
2529
                        JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
2530
                }
2531
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2532
                /* TBD. */
2533
        }
2534
}
2535

    
2536
static void janus_videoroom_recorder_create(janus_videoroom_participant *participant, gboolean audio, gboolean video, gboolean data) {
2537
        char filename[255];
2538
        gint64 now = janus_get_real_time();
2539
        if(audio) {
2540
                memset(filename, 0, 255);
2541
                if(participant->recording_base) {
2542
                        /* Use the filename and path we have been provided */
2543
                        g_snprintf(filename, 255, "%s-audio", participant->recording_base);
2544
                        participant->arc = janus_recorder_create(participant->room->rec_dir,
2545
                                janus_videoroom_audiocodec_name(participant->room->acodec), filename);
2546
                        if(participant->arc == NULL) {
2547
                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2548
                        }
2549
                } else {
2550
                        /* Build a filename */
2551
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
2552
                                participant->room->room_id, participant->user_id, now);
2553
                        participant->arc = janus_recorder_create(participant->room->rec_dir,
2554
                                janus_videoroom_audiocodec_name(participant->room->acodec), filename);
2555
                        if(participant->arc == NULL) {
2556
                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2557
                        }
2558
                }
2559
        }
2560
        if(video) {
2561
                memset(filename, 0, 255);
2562
                if(participant->recording_base) {
2563
                        /* Use the filename and path we have been provided */
2564
                        g_snprintf(filename, 255, "%s-video", participant->recording_base);
2565
                        participant->vrc = janus_recorder_create(participant->room->rec_dir,
2566
                                janus_videoroom_videocodec_name(participant->room->vcodec), filename);
2567
                        if(participant->vrc == NULL) {
2568
                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2569
                        }
2570
                } else {
2571
                        /* Build a filename */
2572
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
2573
                                participant->room->room_id, participant->user_id, now);
2574
                        participant->vrc = janus_recorder_create(participant->room->rec_dir,
2575
                                janus_videoroom_videocodec_name(participant->room->vcodec), filename);
2576
                        if(participant->vrc == NULL) {
2577
                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2578
                        }
2579
                }
2580
        }
2581
        if(data) {
2582
                memset(filename, 0, 255);
2583
                if(participant->recording_base) {
2584
                        /* Use the filename and path we have been provided */
2585
                        g_snprintf(filename, 255, "%s-data", participant->recording_base);
2586
                        participant->drc = janus_recorder_create(participant->room->rec_dir,
2587
                                "text", filename);
2588
                        if(participant->drc == NULL) {
2589
                                JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
2590
                        }
2591
                } else {
2592
                        /* Build a filename */
2593
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-data",
2594
                                participant->room->room_id, participant->user_id, now);
2595
                        participant->drc = janus_recorder_create(participant->room->rec_dir,
2596
                                "text", filename);
2597
                        if(participant->drc == NULL) {
2598
                                JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
2599
                        }
2600
                }
2601
        }
2602
}
2603

    
2604
static void janus_videoroom_recorder_close(janus_videoroom_participant *participant) {
2605
        if(participant->arc) {
2606
                janus_recorder_close(participant->arc);
2607
                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
2608
                janus_recorder_free(participant->arc);
2609
        }
2610
        participant->arc = NULL;
2611
        if(participant->vrc) {
2612
                janus_recorder_close(participant->vrc);
2613
                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
2614
                janus_recorder_free(participant->vrc);
2615
        }
2616
        participant->vrc = NULL;
2617
        if(participant->drc) {
2618
                janus_recorder_close(participant->drc);
2619
                JANUS_LOG(LOG_INFO, "Closed data recording %s\n", participant->drc->filename ? participant->drc->filename : "??");
2620
                janus_recorder_free(participant->drc);
2621
        }
2622
        participant->drc = NULL;
2623
}
2624

    
2625
void janus_videoroom_hangup_media(janus_plugin_session *handle) {
2626
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
2627
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2628
                return;
2629
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2630
        if(!session) {
2631
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2632
                return;
2633
        }
2634
        session->started = FALSE;
2635
        if(session->destroyed)
2636
                return;
2637
        if(g_atomic_int_add(&session->hangingup, 1))
2638
                return;
2639
        /* Send an event to the browser and tell the PeerConnection is over */
2640
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2641
                /* This publisher just 'unpublished' */
2642
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2643
                if(participant->sdp)
2644
                        g_free(participant->sdp);
2645
                participant->sdp = NULL;
2646
                participant->firefox = FALSE;
2647
                participant->audio_active = FALSE;
2648
                participant->video_active = FALSE;
2649
                participant->remb_startup = 4;
2650
                participant->remb_latest = 0;
2651
                participant->fir_latest = 0;
2652
                participant->fir_seq = 0;
2653
                /* Get rid of the recorders, if available */
2654
                janus_mutex_lock(&participant->rec_mutex);
2655
                janus_videoroom_recorder_close(participant);
2656
                janus_mutex_unlock(&participant->rec_mutex);
2657
                janus_mutex_lock(&participant->listeners_mutex);
2658
                while(participant->listeners) {
2659
                        janus_videoroom_listener *l = (janus_videoroom_listener *)participant->listeners->data;
2660
                        if(l) {
2661
                                participant->listeners = g_slist_remove(participant->listeners, l);
2662
                                l->feed = NULL;
2663
                        }
2664
                }
2665
                janus_mutex_unlock(&participant->listeners_mutex);
2666
                janus_videoroom_leave_or_unpublish(participant, FALSE);
2667
                /* Also notify event handlers */
2668
                if(participant->room && gateway->events_is_enabled()) {
2669
                        json_t *info = json_object();
2670
                        json_object_set_new(info, "event", json_string("unpublished"));
2671
                        json_object_set_new(info, "room", json_integer(participant->room->room_id));
2672
                        json_object_set_new(info, "id", json_integer(participant->user_id));
2673
                        gateway->notify_event(&janus_videoroom_plugin, handle, info);
2674
                }
2675
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2676
                /* Get rid of listener */
2677
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2678
                if(listener) {
2679
                        listener->paused = TRUE;
2680
                        janus_videoroom_participant *publisher = listener->feed;
2681
                        if(publisher != NULL) {
2682
                                janus_mutex_lock(&publisher->listeners_mutex);
2683
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
2684
                                janus_mutex_unlock(&publisher->listeners_mutex);
2685
                                listener->feed = NULL;
2686
                                /* Also notify event handlers */
2687
                                if(notify_events && gateway->events_is_enabled()) {
2688
                                        json_t *info = json_object();
2689
                                        json_object_set_new(info, "event", json_string("unsubscribed"));
2690
                                        json_object_set_new(info, "room", json_integer(publisher->room->room_id));
2691
                                        json_object_set_new(info, "feed", json_integer(publisher->user_id));
2692
                                        gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
2693
                                }
2694
                        }
2695
                }
2696
                /* TODO Should we close the handle as well? */
2697
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2698
                /* Do the same, but for all sub-listener */
2699
                janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
2700
                GSList *ps = listener->listeners;
2701
                while(ps) {
2702
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
2703
                        if(l) {
2704
                                l->paused = TRUE;
2705
                                janus_videoroom_participant *publisher = l->feed;
2706
                                if(publisher != NULL) {
2707
                                        janus_mutex_lock(&publisher->listeners_mutex);
2708
                                        publisher->listeners = g_slist_remove(publisher->listeners, l);
2709
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2710
                                        l->feed = NULL;
2711
                                }
2712
                        }
2713
                        /* TODO Should we close the handle as well? */
2714
                        ps = ps->next;
2715
                }
2716
                /* TODO Should we close the handle as well? */
2717
        }
2718
}
2719

    
2720
static void janus_videoroom_sdp_a_format(char *mline, int mline_size, janus_videoroom_audiocodec acodec, int pt, const char *audio_mode, gboolean extmap, int extmap_id) {
2721
        char audio_level_extmap[100];
2722
        if(extmap) {
2723
                /* We only negotiate support (if required) for a single audio extension, audio levels */
2724
                g_snprintf(audio_level_extmap, sizeof(audio_level_extmap),
2725
                        "a=extmap:%d %s\r\n", extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
2726
        }
2727
        switch(acodec) {
2728
                case JANUS_VIDEOROOM_OPUS:
2729
                        g_snprintf(mline, mline_size, sdp_a_template_opus,
2730
                                pt,                                                /* Opus payload type */
2731
                                audio_mode,
2732
                                pt,                                                /* Opus payload type */
2733
                                extmap ? audio_level_extmap : "");
2734
                        break;
2735
                case JANUS_VIDEOROOM_ISAC_32K:
2736
                        g_snprintf(mline, mline_size, sdp_a_template_isac32,
2737
                                pt,                                                /* ISAC 32K payload type */
2738
                                audio_mode,
2739
                                pt,                                         /* ISAC 32K payload type */
2740
                                extmap ? audio_level_extmap : "");
2741
                        break;
2742
                case JANUS_VIDEOROOM_ISAC_16K:
2743
                        g_snprintf(mline, mline_size, sdp_a_template_isac16,
2744
                                pt,                                                /* ISAC 16K payload type */
2745
                                audio_mode,                                /* The publisher gets a recvonly or inactive back */
2746
                                pt,                                                /* ISAC 16K payload type */
2747
                                extmap ? audio_level_extmap : "");
2748
                        break;
2749
                case JANUS_VIDEOROOM_PCMU:
2750
                        g_snprintf(mline, mline_size, sdp_a_template_pcmu,
2751
                                pt,                                                /* PCMU payload type */
2752
                                audio_mode,                                /* The publisher gets a recvonly or inactive back */
2753
                                pt,                                                /* PCMU payload type */
2754
                                extmap ? audio_level_extmap : "");
2755
                        break;
2756
                case JANUS_VIDEOROOM_PCMA:
2757
                        g_snprintf(mline, mline_size, sdp_a_template_pcma,
2758
                                pt,                                                /* PCMA payload type */
2759
                                audio_mode,                                /* The publisher gets a recvonly or inactive back */
2760
                                pt,                                                /* PCMA payload type */
2761
                                extmap ? audio_level_extmap : "");
2762
                        break;
2763
                default:
2764
                        /* Shouldn't happen */
2765
                        mline[0] = '\0';
2766
                        break;
2767
        }
2768
}
2769

    
2770
static void janus_videoroom_sdp_v_format(char *mline, int mline_size, janus_videoroom_videocodec vcodec, int pt, int b, const char *video_mode,
2771
                gboolean vo_extmap, int vo_extmap_id, gboolean pd_extmap, int pd_extmap_id) {
2772
        char extmaps[200], temp[100];
2773
        memset(extmaps, 0, sizeof(extmaps));
2774
        memset(temp, 0, sizeof(temp));
2775
        if(vo_extmap) {
2776
                g_snprintf(temp, sizeof(temp),
2777
                        "a=extmap:%d %s\r\n", vo_extmap_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
2778
                g_strlcat(extmaps, temp, sizeof(extmaps));
2779
        }
2780
        if(pd_extmap) {
2781
                g_snprintf(temp, sizeof(temp),
2782
                        "a=extmap:%d %s\r\n", pd_extmap_id, JANUS_RTP_EXTMAP_PLAYOUT_DELAY);
2783
                g_strlcat(extmaps, temp, sizeof(extmaps));
2784
        }
2785
        switch(vcodec) {
2786
                case JANUS_VIDEOROOM_VP8:
2787
                        g_snprintf(mline, mline_size, sdp_v_template_vp8,
2788
                                pt,                                                        /* payload type */
2789
                                b,                                                        /* Bandwidth */
2790
                                video_mode,                                        /* The publisher gets a recvonly or inactive back */
2791
                                pt,                                                 /* payload type */
2792
                                pt,                                                 /* payload type */
2793
                                pt,                                                 /* payload type */
2794
                                pt,                                                 /* payload type */
2795
                                pt,                                                 /* payload type */
2796
                                (vo_extmap || pd_extmap) ? extmaps : "");
2797
                        break;
2798
                case JANUS_VIDEOROOM_VP9:
2799
                        g_snprintf(mline, mline_size, sdp_v_template_vp9,
2800
                                pt,                                                        /* payload type */
2801
                                b,                                                        /* Bandwidth */
2802
                                video_mode,                                        /* The publisher gets a recvonly or inactive back */
2803
                                pt,                                                 /* payload type */
2804
                                pt,                                                 /* payload type */
2805
                                pt,                                                 /* payload type */
2806
                                pt,                                                 /* payload type */
2807
                                pt,                                                 /* payload type */
2808
                                (vo_extmap || pd_extmap) ? extmaps : "");
2809
                        break;
2810
                case JANUS_VIDEOROOM_H264:
2811
                        g_snprintf(mline, mline_size, sdp_v_template_h264,
2812
                                pt,                                                        /* payload type */
2813
                                b,                                                        /* Bandwidth */
2814
                                video_mode,                                        /* The publisher gets a recvonly or inactive back */
2815
                                pt,                                                 /* payload type */
2816
                                pt,                                                 /* payload type */
2817
                                pt,                                                 /* payload type */
2818
                                pt,                                                 /* payload type */
2819
                                pt,                                                 /* payload type */
2820
                                pt,                                                 /* payload type */
2821
                                (vo_extmap || pd_extmap) ? extmaps : "");
2822
                        break;
2823
                default:
2824
                        /* Shouldn't happen */
2825
                        mline[0] = '\0';
2826
                        break;
2827
        }
2828
}
2829

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

    
3782
                /* Prepare JSON event */
3783
                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3784
                /* Any SDP to handle? */
3785
                const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
3786
                const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
3787
                if(!msg_sdp) {
3788
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3789
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3790
                        json_decref(event);
3791
                } else {
3792
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
3793
                        const char *type = NULL;
3794
                        if(!strcasecmp(msg_sdp_type, "offer")) {
3795
                                /* We need to answer */
3796
                                type = "answer";
3797
                        } else if(!strcasecmp(msg_sdp_type, "answer")) {
3798
                                /* We got an answer (from a listener?), no need to negotiate */
3799
                                g_atomic_int_set(&session->hangingup, 0);
3800
                                int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3801
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3802
                                json_decref(event);
3803
                                janus_videoroom_message_free(msg);
3804
                                continue;
3805
                        } else {
3806
                                /* TODO We don't support anything else right now... */
3807
                                JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg_sdp_type);
3808
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3809
                                g_snprintf(error_cause, 512, "Unknown SDP type '%s'", msg_sdp_type);
3810
                                goto error;
3811
                        }
3812
                        if(session->participant_type != janus_videoroom_p_type_publisher) {
3813
                                /* We shouldn't be here, we always offer ourselves */
3814
                                JANUS_LOG(LOG_ERR, "Only publishers send offers\n");
3815
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3816
                                g_snprintf(error_cause, 512, "Only publishers send offers");
3817
                                goto error;
3818
                        } else {
3819
                                /* This is a new publisher: is there room? */
3820
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
3821
                                janus_videoroom *videoroom = participant->room;
3822
                                int count = 0;
3823
                                GHashTableIter iter;
3824
                                gpointer value;
3825
                                if(!videoroom) {
3826
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3827
                                        goto error;
3828
                                }
3829
                                if(videoroom->destroyed) {
3830
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3831
                                        goto error;
3832
                                }
3833
                                janus_mutex_lock(&videoroom->participants_mutex);
3834
                                g_hash_table_iter_init(&iter, videoroom->participants);
3835
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3836
                                        janus_videoroom_participant *p = value;
3837
                                        if(p != participant && p->sdp)
3838
                                                count++;
3839
                                }
3840
                                janus_mutex_unlock(&videoroom->participants_mutex);
3841
                                if(count == videoroom->max_publishers) {
3842
                                        participant->audio_active = FALSE;
3843
                                        participant->video_active = FALSE;
3844
                                        JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
3845
                                        error_code = JANUS_VIDEOROOM_E