Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ 806f4d41

History | View | Annotate | Download (176 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
record = true|false (whether this room should be recorded, default=false)
68
rec_dir = <folder where recordings should be stored, when enabled>
69
\endverbatim
70
 *
71
 * Note that, due to current limitations in our recording and postprocessing
72
 * code, recording will only work when using VP8 for video in the room.
73
 *
74
 * \section sfuapi Video Room API
75
 * 
76
 * The Video Room API supports several requests, some of which are
77
 * synchronous and some asynchronous. There are some situations, though,
78
 * (invalid JSON, invalid request) which will always result in a
79
 * synchronous error response even for asynchronous requests. 
80
 * 
81
 * \c create , \c destroy , \c exists, \c list and \c listparticipants
82
 * are synchronous requests, which means you'll
83
 * get a response directly within the context of the transaction.
84
 * \c create allows you to create a new video room dynamically, as an
85
 * alternative to using the configuration file; \c destroy removes a
86
 * video room and destroys it, kicking all the users out as part of the
87
 * process; \c exists allows you to check whether a specific video room
88
 * exists; finally, \c list lists all the available rooms, while \c
89
 * listparticipants lists all the participants of a specific room and
90
 * their details.
91
 * 
92
 * The \c join , \c joinandconfigure , \c configure , \c publish ,
93
 * \c unpublish , \c start , \c pause , \c switch , \c stop , \c add ,
94
 * \c remove and \c leave requests instead are all asynchronous, which
95
 * means you'll get a notification about their success or failure in
96
 * an event. \c join allows you to join a specific video room, specifying
97
 * whether that specific PeerConnection will be used for publishing or
98
 * watching; \c configure can be used to modify some of the participation
99
 * settings (e.g., bitrate cap); \c joinandconfigure combines the previous
100
 * two requests in a single one (just for publishers); \c publish can be
101
 * used to start sending media to broadcast to the other participants,
102
 * while \c unpublish does the opposite; \c start allows you to start
103
 * receiving media from a publisher you've subscribed to previously by
104
 * means of a \c join , while \c pause pauses the delivery of the media;
105
 * the \c switch request can be used to change the source of the media
106
 * flowing over a specific PeerConnection (e.g., I was watching Alice,
107
 * I want to watch Bob now) without having to create a new handle for
108
 * that; \c stop interrupts a viewer instance; \c add and \c remove
109
 * are just used when involving "Plan B", and are used to add or remove
110
 * publishers to be muxed in the single viewer PeerConnection; finally,
111
 * \c leave allows you to leave a video room for good.
112
 * 
113
 * Actual API docs: TBD.
114
 * 
115
 * \ingroup plugins
116
 * \ref plugins
117
 */
118

    
119
#include "plugin.h"
120

    
121
#include <jansson.h>
122
#include <sofia-sip/sdp.h>
123

    
124
#include "../debug.h"
125
#include "../apierror.h"
126
#include "../config.h"
127
#include "../mutex.h"
128
#include "../rtp.h"
129
#include "../rtcp.h"
130
#include "../record.h"
131
#include "../utils.h"
132
#include <sys/types.h>
133
#include <sys/socket.h>
134

    
135

    
136
/* Plugin information */
137
#define JANUS_VIDEOROOM_VERSION                        6
138
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.6"
139
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing SFU (Selective Forwarding Unit) for Janus, that is an audio/video router."
140
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
141
#define JANUS_VIDEOROOM_AUTHOR                        "Meetecho s.r.l."
142
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
143

    
144
/* Plugin methods */
145
janus_plugin *create(void);
146
int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
147
void janus_videoroom_destroy(void);
148
int janus_videoroom_get_api_compatibility(void);
149
int janus_videoroom_get_version(void);
150
const char *janus_videoroom_get_version_string(void);
151
const char *janus_videoroom_get_description(void);
152
const char *janus_videoroom_get_name(void);
153
const char *janus_videoroom_get_author(void);
154
const char *janus_videoroom_get_package(void);
155
void janus_videoroom_create_session(janus_plugin_session *handle, int *error);
156
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp);
157
void janus_videoroom_setup_media(janus_plugin_session *handle);
158
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
159
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
160
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len);
161
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video);
162
void janus_videoroom_hangup_media(janus_plugin_session *handle);
163
void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error);
164
char *janus_videoroom_query_session(janus_plugin_session *handle);
165

    
166
/* Plugin setup */
167
static janus_plugin janus_videoroom_plugin =
168
        JANUS_PLUGIN_INIT (
169
                .init = janus_videoroom_init,
170
                .destroy = janus_videoroom_destroy,
171

    
172
                .get_api_compatibility = janus_videoroom_get_api_compatibility,
173
                .get_version = janus_videoroom_get_version,
174
                .get_version_string = janus_videoroom_get_version_string,
175
                .get_description = janus_videoroom_get_description,
176
                .get_name = janus_videoroom_get_name,
177
                .get_author = janus_videoroom_get_author,
178
                .get_package = janus_videoroom_get_package,
179
                
180
                .create_session = janus_videoroom_create_session,
181
                .handle_message = janus_videoroom_handle_message,
182
                .setup_media = janus_videoroom_setup_media,
183
                .incoming_rtp = janus_videoroom_incoming_rtp,
184
                .incoming_rtcp = janus_videoroom_incoming_rtcp,
185
                .incoming_data = janus_videoroom_incoming_data,
186
                .slow_link = janus_videoroom_slow_link,
187
                .hangup_media = janus_videoroom_hangup_media,
188
                .destroy_session = janus_videoroom_destroy_session,
189
                .query_session = janus_videoroom_query_session,
190
        );
191

    
192
/* Plugin creator */
193
janus_plugin *create(void) {
194
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
195
        return &janus_videoroom_plugin;
196
}
197

    
198
/* Parameter validation */
199
static struct janus_json_parameter request_parameters[] = {
200
        {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
201
};
202
static struct janus_json_parameter create_parameters[] = {
203
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
204
        {"description", JSON_STRING, 0},
205
        {"is_private", JANUS_JSON_BOOL, 0},
206
        {"secret", JSON_STRING, 0},
207
        {"pin", JSON_STRING, 0},
208
        {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
209
        {"fir_freq", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
210
        {"publishers", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
211
        {"audiocodec", JSON_STRING, 0},
212
        {"videocodec", JSON_STRING, 0},
213
        {"record", JANUS_JSON_BOOL, 0},
214
        {"rec_dir", JSON_STRING, 0},
215
        {"permanent", JANUS_JSON_BOOL, 0}
216
};
217
static struct janus_json_parameter room_parameters[] = {
218
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
219
};
220
static struct janus_json_parameter destroy_parameters[] = {
221
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
222
        {"permanent", JANUS_JSON_BOOL, 0}
223
};
224
static struct janus_json_parameter join_parameters[] = {
225
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
226
        {"ptype", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
227
        {"audio", JANUS_JSON_BOOL, 0},
228
        {"video", JANUS_JSON_BOOL, 0},
229
        {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
230
        {"record", JANUS_JSON_BOOL, 0},
231
        {"filename", JSON_STRING, 0}
232
};
233
static struct janus_json_parameter publish_parameters[] = {
234
        {"audio", JANUS_JSON_BOOL, 0},
235
        {"video", JANUS_JSON_BOOL, 0},
236
        {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
237
        {"record", JANUS_JSON_BOOL, 0},
238
        {"filename", JSON_STRING, 0}
239
};
240
static struct janus_json_parameter rtp_forward_parameters[] = {
241
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
242
        {"publisher_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
243
        {"vid_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
244
        {"au_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
245
        {"host", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
246
};
247
static struct janus_json_parameter stop_rtp_forward_parameters[] = {
248
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
249
        {"publisher_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
250
        {"stream_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
251
};
252
static struct janus_json_parameter publisher_parameters[] = {
253
        {"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
254
        {"display", JSON_STRING, 0}
255
};
256
static struct janus_json_parameter configure_parameters[] = {
257
        {"audio", JANUS_JSON_BOOL, 0},
258
        {"video", JANUS_JSON_BOOL, 0},
259
        {"data", JANUS_JSON_BOOL, 0}
260
};
261
static struct janus_json_parameter listener_parameters[] = {
262
        {"feed", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
263
        {"audio", JANUS_JSON_BOOL, 0},
264
        {"video", JANUS_JSON_BOOL, 0},
265
        {"data", JANUS_JSON_BOOL, 0}
266
};
267
static struct janus_json_parameter feeds_parameters[] = {
268
        {"feeds", JSON_ARRAY, JANUS_JSON_PARAM_NONEMPTY}
269
};
270

    
271
/* Static configuration instance */
272
static janus_config *config = NULL;
273
static const char *config_folder = NULL;
274
static janus_mutex config_mutex;
275

    
276
/* Useful stuff */
277
static volatile gint initialized = 0, stopping = 0;
278
static janus_callbacks *gateway = NULL;
279
static GThread *handler_thread;
280
static GThread *watchdog;
281
static su_home_t *sdphome = NULL;
282
static void *janus_videoroom_handler(void *data);
283
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
284
static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data);
285

    
286
typedef enum janus_videoroom_p_type {
287
        janus_videoroom_p_type_none = 0,
288
        janus_videoroom_p_type_subscriber,                        /* Generic listener/subscriber */
289
        janus_videoroom_p_type_subscriber_muxed,        /* Multiplexed listener/subscriber */
290
        janus_videoroom_p_type_publisher,                        /* Participant/publisher */
291
} janus_videoroom_p_type;
292

    
293
typedef struct janus_videoroom_message {
294
        janus_plugin_session *handle;
295
        char *transaction;
296
        json_t *message;
297
        char *sdp_type;
298
        char *sdp;
299
} janus_videoroom_message;
300
static GAsyncQueue *messages = NULL;
301
static janus_videoroom_message exit_message;
302

    
303
static void janus_videoroom_message_free(janus_videoroom_message *msg) {
304
        if(!msg || msg == &exit_message)
305
                return;
306

    
307
        msg->handle = NULL;
308

    
309
        g_free(msg->transaction);
310
        msg->transaction = NULL;
311
        if(msg->message)
312
                json_decref(msg->message);
313
        msg->message = NULL;
314
        g_free(msg->sdp_type);
315
        msg->sdp_type = NULL;
316
        g_free(msg->sdp);
317
        msg->sdp = NULL;
318

    
319
        g_free(msg);
320
}
321

    
322
typedef enum janus_videoroom_audiocodec {
323
        JANUS_VIDEOROOM_OPUS,                /* Publishers will have to use OPUS         */
324
        JANUS_VIDEOROOM_ISAC_32K,        /* Publishers will have to use ISAC 32K */
325
        JANUS_VIDEOROOM_ISAC_16K,        /* Publishers will have to use ISAC 16K */
326
        JANUS_VIDEOROOM_PCMU,                /* Publishers will have to use PCMU 8K         */
327
        JANUS_VIDEOROOM_PCMA                /* Publishers will have to use PCMA 8K         */
328
} janus_videoroom_audiocodec;
329
static const char *janus_videoroom_audiocodec_name(janus_videoroom_audiocodec acodec) {
330
        switch(acodec) {
331
                case JANUS_VIDEOROOM_OPUS:
332
                        return "opus";
333
                case JANUS_VIDEOROOM_ISAC_32K:
334
                        return "isac32";
335
                case JANUS_VIDEOROOM_ISAC_16K:
336
                        return "isac16";
337
                case JANUS_VIDEOROOM_PCMU:
338
                        return "pcmu";
339
                case JANUS_VIDEOROOM_PCMA:
340
                        return "pcma";
341
                default:
342
                        /* Shouldn't happen */
343
                        return "opus";
344
        }
345
}
346

    
347
typedef enum janus_videoroom_videocodec {
348
        JANUS_VIDEOROOM_VP8,        /* Publishers will have to use VP8 */
349
        JANUS_VIDEOROOM_VP9,        /* Publishers will have to use VP9 */
350
        JANUS_VIDEOROOM_H264        /* Publishers will have to use H264 */
351
} janus_videoroom_videocodec;
352
static const char *janus_videoroom_videocodec_name(janus_videoroom_videocodec vcodec) {
353
        switch(vcodec) {
354
                case JANUS_VIDEOROOM_VP8:
355
                        return "vp8";
356
                case JANUS_VIDEOROOM_VP9:
357
                        return "vp9";
358
                case JANUS_VIDEOROOM_H264:
359
                        return "h264";
360
                default:
361
                        /* Shouldn't happen */
362
                        return "vp8";
363
        }
364
}
365

    
366
typedef struct janus_videoroom {
367
        guint64 room_id;                        /* Unique room ID */
368
        gchar *room_name;                        /* Room description */
369
        gchar *room_secret;                        /* Secret needed to manipulate (e.g., destroy) this room */
370
        gchar *room_pin;                        /* Password needed to join this room, if any */
371
        gboolean is_private;                /* Whether this room is 'private' (as in hidden) or not */
372
        int max_publishers;                        /* Maximum number of concurrent publishers */
373
        uint64_t bitrate;                        /* Global bitrate limit */
374
        uint16_t fir_freq;                        /* Regular FIR frequency (0=disabled) */
375
        janus_videoroom_audiocodec acodec;        /* Audio codec to force on publishers*/
376
        janus_videoroom_videocodec vcodec;        /* Video codec to force on publishers*/
377
        gboolean record;                        /* Whether the feeds from publishers in this room should be recorded */
378
        char *rec_dir;                                /* Where to save the recordings of this room, if enabled */
379
        gint64 destroyed;                        /* Value to flag the room for destruction, done lazily */
380
        GHashTable *participants;        /* Map of potential publishers (we get listeners from them) */
381
        janus_mutex participants_mutex;/* Mutex to protect room properties */
382
} janus_videoroom;
383
static GHashTable *rooms;
384
static janus_mutex rooms_mutex;
385
static GList *old_rooms;
386
static void janus_videoroom_free(janus_videoroom *room);
387

    
388
typedef struct janus_videoroom_session {
389
        janus_plugin_session *handle;
390
        janus_videoroom_p_type participant_type;
391
        gpointer participant;
392
        gboolean started;
393
        gboolean stopping;
394
        volatile gint hangingup;
395
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
396
} janus_videoroom_session;
397
static GHashTable *sessions;
398
static GList *old_sessions;
399
static janus_mutex sessions_mutex;
400

    
401
/* a host whose ports gets streamed rtp packets of the corresponding type. */
402
typedef struct rtp_forwarder {
403
        int is_video;
404
        struct sockaddr_in serv_addr;
405
} rtp_forwarder;
406

    
407
typedef struct janus_videoroom_participant {
408
        janus_videoroom_session *session;
409
        janus_videoroom *room;        /* Room */
410
        guint64 user_id;        /* Unique ID in the room */
411
        gchar *display;        /* Display name (just for fun) */
412
        gchar *sdp;                        /* The SDP this publisher negotiated, if any */
413
        gboolean audio, video, data;                /* Whether audio, video and/or data is going to be sent by this publisher */
414
        guint32 audio_pt;                /* Audio payload type (Opus) */
415
        guint32 video_pt;                /* Video payload type (depends on room configuration) */
416
        guint32 audio_ssrc;                /* Audio SSRC of this publisher */
417
        guint32 video_ssrc;                /* Video SSRC of this publisher */
418
        gboolean audio_active;
419
        gboolean video_active;
420
        gboolean firefox;        /* We send Firefox users a different kind of FIR */
421
        uint64_t bitrate;
422
        gint64 remb_startup;/* Incremental changes on REMB to reach the target at startup */
423
        gint64 remb_latest;        /* Time of latest sent REMB (to avoid flooding) */
424
        gint64 fir_latest;        /* Time of latest sent FIR (to avoid flooding) */
425
        gint fir_seq;                /* FIR sequence number */
426
        gboolean recording_active;        /* Whether this publisher has to be recorded or not */
427
        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 */
428
        janus_recorder *arc;        /* The Janus recorder instance for this publisher's audio, if enabled */
429
        janus_recorder *vrc;        /* The Janus recorder instance for this publisher's video, if enabled */
430
        GSList *listeners;
431
        janus_mutex listeners_mutex;
432
        GHashTable *rtp_forwarders;
433
        janus_mutex rtp_forwarders_mutex;
434
        int udp_sock; /* The udp socket on which to forward rtp packets */
435
} janus_videoroom_participant;
436
static void janus_videoroom_participant_free(janus_videoroom_participant *p);
437
static void janus_rtp_forwarder_free_helper(gpointer data);
438
static guint32 janus_rtp_forwarder_add_helper(janus_videoroom_participant *p, const gchar* host, int port, int is_video);
439
typedef struct janus_videoroom_listener_context {
440
        /* Needed to fix seq and ts in case of publisher switching */
441
        uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev,
442
                        v_last_ssrc, v_last_ts, v_base_ts, v_base_ts_prev;
443
        uint16_t a_last_seq, a_base_seq, a_base_seq_prev,
444
                        v_last_seq, v_base_seq, v_base_seq_prev;
445
} janus_videoroom_listener_context;
446

    
447
typedef struct janus_videoroom_listener {
448
        janus_videoroom_session *session;
449
        janus_videoroom *room;        /* Room */
450
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
451
        janus_videoroom_listener_context context;        /* Needed in case there are publisher switches on this listener */
452
        gboolean audio, video, data;                /* Whether audio, video and/or data must be sent to this publisher */
453
        struct janus_videoroom_listener_muxed *parent;        /* Overall subscriber, if this is a sub-listener in a Multiplexed one */
454
        gboolean paused;
455
} janus_videoroom_listener;
456
static void janus_videoroom_listener_free(janus_videoroom_listener *l);
457

    
458
typedef struct janus_videoroom_listener_muxed {
459
        janus_videoroom_session *session;
460
        janus_videoroom *room;        /* Room */
461
        GSList *listeners;        /* List of listeners (as a Multiplexed listener can be subscribed to more publishers at the same time) */
462
        janus_mutex listeners_mutex;
463
} janus_videoroom_listener_muxed;
464
static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l);
465

    
466
typedef struct janus_videoroom_rtp_relay_packet {
467
        rtp_header *data;
468
        gint length;
469
        gint is_video;
470
        uint32_t timestamp;
471
        uint16_t seq_number;
472
} janus_videoroom_rtp_relay_packet;
473

    
474
/* SDP offer/answer templates */
475
#define OPUS_PT        111
476
#define ISAC32_PT        104
477
#define ISAC16_PT        103
478
#define PCMU_PT        0
479
#define PCMA_PT        8
480
#define VP8_PT                100
481
#define VP9_PT                101
482
#define H264_PT        107
483
#define sdp_template \
484
                "v=0\r\n" \
485
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */ \
486
                "s=%s\r\n"                                                        /* Video room name */ \
487
                "t=0 0\r\n" \
488
                "%s%s%s"                                /* Audio, video and/or data channel m-lines */
489
#define sdp_a_template_opus \
490
                "m=audio 1 RTP/SAVPF %d\r\n"                /* Opus payload type */ \
491
                "c=IN IP4 1.1.1.1\r\n" \
492
                "a=%s\r\n"                                                        /* Media direction */ \
493
                "a=rtpmap:%d opus/48000/2\r\n"                /* Opus payload type */
494
#define sdp_a_template_isac32 \
495
                "m=audio 1 RTP/SAVPF %d\r\n"                /* ISAC32_PT payload type */ \
496
                "c=IN IP4 1.1.1.1\r\n" \
497
                "a=%s\r\n"                                                        /* Media direction */ \
498
                "a=rtpmap:%d ISAC/32000\r\n"                /* ISAC32_PT payload type */
499
#define sdp_a_template_isac16 \
500
                "m=audio 1 RTP/SAVPF %d\r\n"                /* ISAC16_PT payload type */ \
501
                "c=IN IP4 1.1.1.1\r\n" \
502
                "a=%s\r\n"                                                        /* Media direction */ \
503
                "a=rtpmap:%d ISAC/16000\r\n"                /* ISAC16_PT payload type */
504
#define sdp_a_template_pcmu \
505
                "m=audio 1 RTP/SAVPF %d\r\n"                /* PCMU_PT payload type */ \
506
                "c=IN IP4 1.1.1.1\r\n" \
507
                "a=%s\r\n"                                                        /* Media direction */ \
508
                "a=rtpmap:%d PCMU/8000\r\n"                    /* PCMU_PT payload type */
509
#define sdp_a_template_pcma \
510
                "m=audio 1 RTP/SAVPF %d\r\n"                /* PCMA_PT payload type */ \
511
                "c=IN IP4 1.1.1.1\r\n" \
512
                "a=%s\r\n"                                                        /* Media direction */ \
513
                "a=rtpmap:%d PCMA/8000\r\n"                    /* PCMA_PT payload type */
514
#define sdp_v_template_vp8 \
515
                "m=video 1 RTP/SAVPF %d\r\n"                /* VP8 payload type */ \
516
                "c=IN IP4 1.1.1.1\r\n" \
517
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
518
                "a=%s\r\n"                                                        /* Media direction */ \
519
                "a=rtpmap:%d VP8/90000\r\n"                        /* VP8 payload type */ \
520
                "a=rtcp-fb:%d ccm fir\r\n"                        /* VP8 payload type */ \
521
                "a=rtcp-fb:%d nack\r\n"                                /* VP8 payload type */ \
522
                "a=rtcp-fb:%d nack pli\r\n"                        /* VP8 payload type */ \
523
                "a=rtcp-fb:%d goog-remb\r\n"                /* VP8 payload type */
524
#define sdp_v_template_vp9 \
525
                "m=video 1 RTP/SAVPF %d\r\n"                /* VP9 payload type */ \
526
                "c=IN IP4 1.1.1.1\r\n" \
527
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
528
                "a=%s\r\n"                                                        /* Media direction */ \
529
                "a=rtpmap:%d VP9/90000\r\n"                        /* VP9 payload type */ \
530
                "a=rtcp-fb:%d ccm fir\r\n"                        /* VP9 payload type */ \
531
                "a=rtcp-fb:%d nack\r\n"                                /* VP9 payload type */ \
532
                "a=rtcp-fb:%d nack pli\r\n"                        /* VP9 payload type */ \
533
                "a=rtcp-fb:%d goog-remb\r\n"                /* VP9 payload type */
534
#define sdp_v_template_h264 \
535
                "m=video 1 RTP/SAVPF %d\r\n"                /* H264 payload type */ \
536
                "c=IN IP4 1.1.1.1\r\n" \
537
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
538
                "a=%s\r\n"                                                        /* Media direction */ \
539
                "a=rtpmap:%d H264/90000\r\n"                /* H264 payload type */ \
540
                "a=fmtp:%d profile-level-id=42e01f;packetization-mode=1\r\n" \
541
                "a=rtcp-fb:%d ccm fir\r\n"                        /* H264 payload type */ \
542
                "a=rtcp-fb:%d nack\r\n"                                /* H264 payload type */ \
543
                "a=rtcp-fb:%d nack pli\r\n"                        /* H264 payload type */ \
544
                "a=rtcp-fb:%d goog-remb\r\n"                /* H264 payload type */
545
#define sdp_d_template \
546
                "m=application 1 DTLS/SCTP 5000\r\n" \
547
                "c=IN IP4 1.1.1.1\r\n" \
548
                "a=sctpmap:5000 webrtc-datachannel 16\r\n"
549

    
550

    
551
/* Error codes */
552
#define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR                499
553
#define JANUS_VIDEOROOM_ERROR_NO_MESSAGE                421
554
#define JANUS_VIDEOROOM_ERROR_INVALID_JSON                422
555
#define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST        423
556
#define JANUS_VIDEOROOM_ERROR_JOIN_FIRST                424
557
#define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED        425
558
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM                426
559
#define JANUS_VIDEOROOM_ERROR_ROOM_EXISTS                427
560
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED                428
561
#define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT        429
562
#define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT        430
563
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE        431
564
#define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL        432
565
#define JANUS_VIDEOROOM_ERROR_UNAUTHORIZED                433
566
#define JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED        434
567
#define JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED                435
568
#define JANUS_VIDEOROOM_ERROR_ID_EXISTS                        436
569
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP                437
570

    
571

    
572
/* Multiplexing helpers */
573
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
574
int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
575
int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, char *event_text);
576

    
577
static guint32 janus_rtp_forwarder_add_helper(janus_videoroom_participant *p, const gchar* host, int port, int is_video) {
578
        if(!p || !host) {
579
                return 0;
580
        }
581
        rtp_forwarder *forward = g_malloc0(sizeof(rtp_forwarder));
582
        forward->is_video = is_video;
583
        forward->serv_addr.sin_family = AF_INET;
584
        inet_pton(AF_INET, host, &(forward->serv_addr.sin_addr));
585
        forward->serv_addr.sin_port = htons(port);
586
        janus_mutex_lock(&p->rtp_forwarders_mutex);
587
        guint32 stream_id = g_random_int();
588
        while(g_hash_table_lookup(p->rtp_forwarders, GUINT_TO_POINTER(stream_id)) != NULL) {
589
                stream_id = g_random_int();
590
        }
591
        g_hash_table_insert(p->rtp_forwarders, GUINT_TO_POINTER(stream_id), forward);
592
        janus_mutex_unlock(&p->rtp_forwarders_mutex);
593
        JANUS_LOG(LOG_VERB, "Added %s rtp_forward to participant %"SCNu64" host: %s:%d stream_id: %"SCNu32"\n", is_video ? "video":"audio", p->user_id, host, port, stream_id);
594
        return stream_id;
595
}
596

    
597

    
598
/* Convenience function for freeing a session */
599
static void session_free(gpointer data) {
600
        if(data) {
601
                janus_videoroom_session* session = (janus_videoroom_session*)data;
602
                switch(session->participant_type) {
603
                case janus_videoroom_p_type_publisher: 
604
                        janus_videoroom_participant_free(session->participant);
605
                        break;   
606
                case janus_videoroom_p_type_subscriber:
607
                        janus_videoroom_listener_free(session->participant);
608
                        break;
609
                case janus_videoroom_p_type_subscriber_muxed:
610
                        janus_videoroom_muxed_listener_free(session->participant);
611
                        break;
612
                default:
613
                        break;
614
                }
615
                session->handle = NULL;
616
                g_free(session);
617
                session = NULL;
618
        }
619
}
620

    
621
static void janus_rtp_forwarder_free_helper(gpointer data) {
622
        if(data) {
623
                rtp_forwarder* forward = (rtp_forwarder*)data;
624
                if(forward) {
625
                        g_free(forward);
626
                        forward = NULL;
627
                }
628
        }
629
}
630

    
631
/* Convenience wrapper function for session_free that corresponds to GHRFunc() format for hash table cleanup */
632
static gboolean session_hash_table_remove(gpointer key, gpointer value, gpointer not_used) {
633
        if(value) {
634
                session_free(value);
635
        }
636
        return TRUE;
637
}
638

    
639
/* VideoRoom watchdog/garbage collector (sort of) */
640
void *janus_videoroom_watchdog(void *data);
641
void *janus_videoroom_watchdog(void *data) {
642
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog started\n");
643
        gint64 now = 0, room_now = 0;
644
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
645
                janus_mutex_lock(&sessions_mutex);
646
                /* Iterate on all the participants/listeners and check if we need to remove any of them */
647
                now = janus_get_monotonic_time();
648
                if(old_sessions != NULL) {
649
                        GList *sl = old_sessions;
650
                        JANUS_LOG(LOG_HUGE, "Checking %d old VideoRoom sessions...\n", g_list_length(old_sessions));
651
                        while(sl) {
652
                                janus_videoroom_session *session = (janus_videoroom_session *)sl->data;
653
                                /* If we are stopping, their is no point to continue to iterate */
654
                                if(!initialized || stopping) {
655
                                        break;
656
                                }
657
                                if(!session) {
658
                                        sl = sl->next;
659
                                        continue;
660
                                }
661
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
662
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
663
                                        JANUS_LOG(LOG_VERB, "Freeing old VideoRoom session\n");
664
                                        GList *rm = sl->next;
665
                                        old_sessions = g_list_delete_link(old_sessions, sl);
666
                                        sl = rm;
667
                                        g_hash_table_steal(sessions, session->handle);
668
                                        session_free(session);
669
                                        continue;
670
                                }
671
                                sl = sl->next;
672
                        }
673
                }
674
                janus_mutex_unlock(&sessions_mutex);
675
                janus_mutex_lock(&rooms_mutex);
676
                if(old_rooms != NULL) {
677
                        GList *rl = old_rooms;
678
                        room_now = janus_get_monotonic_time();
679
                        while(rl) {
680
                                janus_videoroom* room = (janus_videoroom*)rl->data;
681
                                if(!initialized || stopping){
682
                                        break;
683
                                }
684
                                if(!room) {
685
                                        rl = rl->next;
686
                                        continue;
687
                                }
688
                                if(room_now - room->destroyed >= 5*G_USEC_PER_SEC) {
689
                                        GList *rm = rl->next;
690
                                        old_rooms = g_list_delete_link(old_rooms, rl);
691
                                        rl = rm;
692
                                        g_hash_table_remove(rooms, GUINT_TO_POINTER(room->room_id));
693
                                        continue;
694
                                }
695
                                rl = rl->next;
696
                        }
697
                }
698
                janus_mutex_unlock(&rooms_mutex);
699
                g_usleep(500000);
700
        }
701
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog stopped\n");
702
        return NULL;
703
}
704

    
705

    
706
/* Plugin implementation */
707
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
708
        if(g_atomic_int_get(&stopping)) {
709
                /* Still stopping from before */
710
                return -1;
711
        }
712
        if(callback == NULL || config_path == NULL) {
713
                /* Invalid arguments */
714
                return -1;
715
        }
716
        sdphome = su_home_new(sizeof(su_home_t));
717
        if(su_home_init(sdphome) < 0) {
718
                JANUS_LOG(LOG_FATAL, "Ops, error setting up sofia-sdp?\n");
719
                return -1;
720
        }
721

    
722
        /* Read configuration */
723
        char filename[255];
724
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
725
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
726
        config = janus_config_parse(filename);
727
        config_folder = config_path;
728
        if(config != NULL)
729
                janus_config_print(config);
730
        janus_mutex_init(&config_mutex);
731

    
732
        rooms = g_hash_table_new_full(NULL, NULL, NULL,
733
                                      (GDestroyNotify) janus_videoroom_free);
734
        janus_mutex_init(&rooms_mutex);
735
        sessions = g_hash_table_new(NULL, NULL);
736
        janus_mutex_init(&sessions_mutex);
737

    
738
        messages = g_async_queue_new_full((GDestroyNotify) janus_videoroom_message_free);
739

    
740
        /* This is the callback we'll need to invoke to contact the gateway */
741
        gateway = callback;
742

    
743
        /* Parse configuration to populate the rooms list */
744
        if(config != NULL) {
745
                GList *cl = janus_config_get_categories(config);
746
                while(cl != NULL) {
747
                        janus_config_category *cat = (janus_config_category *)cl->data;
748
                        if(cat->name == NULL) {
749
                                cl = cl->next;
750
                                continue;
751
                        }
752
                        JANUS_LOG(LOG_VERB, "Adding video room '%s'\n", cat->name);
753
                        janus_config_item *desc = janus_config_get_item(cat, "description");
754
                        janus_config_item *priv = janus_config_get_item(cat, "is_private");
755
                        janus_config_item *secret = janus_config_get_item(cat, "secret");
756
                        janus_config_item *pin = janus_config_get_item(cat, "pin");
757
                        janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
758
                        janus_config_item *maxp = janus_config_get_item(cat, "publishers");
759
                        janus_config_item *firfreq = janus_config_get_item(cat, "fir_freq");
760
                        janus_config_item *audiocodec = janus_config_get_item(cat, "audiocodec");
761
                        janus_config_item *videocodec = janus_config_get_item(cat, "videocodec");
762
                        janus_config_item *record = janus_config_get_item(cat, "record");
763
                        janus_config_item *rec_dir = janus_config_get_item(cat, "rec_dir");
764
                        /* Create the video room */
765
                        janus_videoroom *videoroom = g_malloc0(sizeof(janus_videoroom));
766
                        if(videoroom == NULL) {
767
                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
768
                                continue;
769
                        }
770
                        videoroom->room_id = atol(cat->name);
771
                        char *description = NULL;
772
                        if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
773
                                description = g_strdup(desc->value);
774
                        else
775
                                description = g_strdup(cat->name);
776
                        if(description == NULL) {
777
                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
778
                                continue;
779
                        }
780
                        videoroom->room_name = description;
781
                        if(secret != NULL && secret->value != NULL) {
782
                                videoroom->room_secret = g_strdup(secret->value);
783
                        }
784
                        if(pin != NULL && pin->value != NULL) {
785
                                videoroom->room_pin = g_strdup(pin->value);
786
                        }
787
                        videoroom->is_private = priv && priv->value && janus_is_true(priv->value);
788
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
789
                        if(maxp != NULL && maxp->value != NULL)
790
                                videoroom->max_publishers = atol(maxp->value);
791
                        if(videoroom->max_publishers < 0)
792
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
793
                        videoroom->bitrate = 0;
794
                        if(bitrate != NULL && bitrate->value != NULL)
795
                                videoroom->bitrate = atol(bitrate->value);
796
                        if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
797
                                videoroom->bitrate = 64000;        /* Don't go below 64k */
798
                        videoroom->fir_freq = 0;
799
                        if(firfreq != NULL && firfreq->value != NULL)
800
                                videoroom->fir_freq = atol(firfreq->value);
801
                        videoroom->acodec = JANUS_VIDEOROOM_OPUS;
802
                        if(audiocodec && audiocodec->value) {
803
                                if(!strcasecmp(audiocodec->value, ""))
804
                                        videoroom->acodec = JANUS_VIDEOROOM_OPUS;
805
                                else if(!strcasecmp(audiocodec->value, "isac32"))
806
                                        videoroom->acodec = JANUS_VIDEOROOM_ISAC_32K;
807
                                else if(!strcasecmp(audiocodec->value, "isac16"))
808
                                        videoroom->acodec = JANUS_VIDEOROOM_ISAC_16K;
809
                                else if(!strcasecmp(audiocodec->value, "pcmu"))
810
                                        videoroom->acodec = JANUS_VIDEOROOM_PCMU;
811
                                else if(!strcasecmp(audiocodec->value, "pcma"))
812
                                        videoroom->acodec = JANUS_VIDEOROOM_PCMA;
813
                                else {
814
                                        JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s', falling back to OPUS\n", audiocodec->value);
815
                                        videoroom->acodec = JANUS_VIDEOROOM_OPUS;
816
                                }
817
                        }
818
                        videoroom->vcodec = JANUS_VIDEOROOM_VP8;
819
                        if(videocodec && videocodec->value) {
820
                                if(!strcasecmp(videocodec->value, "vp8"))
821
                                        videoroom->vcodec = JANUS_VIDEOROOM_VP8;
822
                                else if(!strcasecmp(videocodec->value, "vp9"))
823
                                        videoroom->vcodec = JANUS_VIDEOROOM_VP9;
824
                                else if(!strcasecmp(videocodec->value, "h264"))
825
                                        videoroom->vcodec = JANUS_VIDEOROOM_H264;
826
                                else {
827
                                        JANUS_LOG(LOG_WARN, "Unsupported video codec '%s', falling back to VP8\n", videocodec->value);
828
                                        videoroom->vcodec = JANUS_VIDEOROOM_VP8;
829
                                }
830
                        }
831
                        if(record && record->value) {
832
                                videoroom->record = janus_is_true(record->value);
833
                        }
834
                        if(rec_dir && rec_dir->value) {
835
                                videoroom->rec_dir = g_strdup(rec_dir->value);
836
                        }
837
                        videoroom->destroyed = 0;
838
                        janus_mutex_init(&videoroom->participants_mutex);
839
                        videoroom->participants = g_hash_table_new(NULL, NULL);
840
                        janus_mutex_lock(&rooms_mutex);
841
                        g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
842
                        janus_mutex_unlock(&rooms_mutex);
843
                        JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, %s/%s codecs, secret: %s, pin: %s)\n",
844
                                videoroom->room_id, videoroom->room_name,
845
                                videoroom->is_private ? "private" : "public",
846
                                janus_videoroom_audiocodec_name(videoroom->acodec),
847
                                janus_videoroom_videocodec_name(videoroom->vcodec),
848
                                videoroom->room_secret ? videoroom->room_secret : "no secret",
849
                                videoroom->room_pin ? videoroom->room_pin : "no pin");
850
                        if(videoroom->record) {
851
                                JANUS_LOG(LOG_VERB, "  -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
852
                        }
853
                        cl = cl->next;
854
                }
855
                /* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
856
        }
857

    
858
        /* Show available rooms */
859
        janus_mutex_lock(&rooms_mutex);
860
        GHashTableIter iter;
861
        gpointer value;
862
        g_hash_table_iter_init(&iter, rooms);
863
        while (g_hash_table_iter_next(&iter, NULL, &value)) {
864
                janus_videoroom *vr = value;
865
                JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds, %s audio codec, %s video codec\n",
866
                        vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq,
867
                        janus_videoroom_audiocodec_name(vr->acodec), janus_videoroom_videocodec_name(vr->vcodec));
868
        }
869
        janus_mutex_unlock(&rooms_mutex);
870

    
871
        g_atomic_int_set(&initialized, 1);
872

    
873
        GError *error = NULL;
874
        /* Start the sessions watchdog */
875
        watchdog = g_thread_try_new("vroom watchdog", &janus_videoroom_watchdog, NULL, &error);
876
        if(error != NULL) {
877
                g_atomic_int_set(&initialized, 0);
878
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom watchdog thread...\n", error->code, error->message ? error->message : "??");
879
                janus_config_destroy(config);
880
                return -1;
881
        }
882
        /* Launch the thread that will handle incoming messages */
883
        handler_thread = g_thread_try_new("janus videoroom handler", janus_videoroom_handler, NULL, &error);
884
        if(error != NULL) {
885
                g_atomic_int_set(&initialized, 0);
886
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom handler thread...\n", error->code, error->message ? error->message : "??");
887
                janus_config_destroy(config);
888
                return -1;
889
        }
890
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
891
        return 0;
892
}
893

    
894
void janus_videoroom_destroy(void) {
895
        if(!g_atomic_int_get(&initialized))
896
                return;
897
        g_atomic_int_set(&stopping, 1);
898

    
899
        g_async_queue_push(messages, &exit_message);
900
        if(handler_thread != NULL) {
901
                g_thread_join(handler_thread);
902
                handler_thread = NULL;
903
        }
904
        if(watchdog != NULL) {
905
                g_thread_join(watchdog);
906
                watchdog = NULL;
907
        }
908
        su_home_deinit(sdphome);
909
        su_home_unref(sdphome);
910
        sdphome = NULL;
911

    
912
        /* FIXME We should destroy the sessions cleanly */
913
        janus_mutex_lock(&sessions_mutex);
914
        g_hash_table_foreach_remove(sessions, (GHRFunc)session_hash_table_remove, NULL);
915
        g_hash_table_destroy(sessions);
916
        sessions = NULL;
917
        janus_mutex_unlock(&sessions_mutex);
918

    
919
        janus_mutex_lock(&rooms_mutex);
920

    
921
        g_hash_table_destroy(rooms);
922
        rooms = NULL;
923
        janus_mutex_unlock(&rooms_mutex);
924
        janus_mutex_destroy(&rooms_mutex);
925

    
926
        g_async_queue_unref(messages);
927
        messages = NULL;
928

    
929
        janus_config_destroy(config);
930

    
931
        g_atomic_int_set(&initialized, 0);
932
        g_atomic_int_set(&stopping, 0);
933
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
934
}
935

    
936
int janus_videoroom_get_api_compatibility(void) {
937
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
938
        return JANUS_PLUGIN_API_VERSION;
939
}
940

    
941
int janus_videoroom_get_version(void) {
942
        return JANUS_VIDEOROOM_VERSION;
943
}
944

    
945
const char *janus_videoroom_get_version_string(void) {
946
        return JANUS_VIDEOROOM_VERSION_STRING;
947
}
948

    
949
const char *janus_videoroom_get_description(void) {
950
        return JANUS_VIDEOROOM_DESCRIPTION;
951
}
952

    
953
const char *janus_videoroom_get_name(void) {
954
        return JANUS_VIDEOROOM_NAME;
955
}
956

    
957
const char *janus_videoroom_get_author(void) {
958
        return JANUS_VIDEOROOM_AUTHOR;
959
}
960

    
961
const char *janus_videoroom_get_package(void) {
962
        return JANUS_VIDEOROOM_PACKAGE;
963
}
964

    
965
void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
966
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
967
                *error = -1;
968
                return;
969
        }        
970
        janus_videoroom_session *session = (janus_videoroom_session *)g_malloc0(sizeof(janus_videoroom_session));
971
        if(session == NULL) {
972
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
973
                *error = -2;
974
                return;
975
        }
976
        session->handle = handle;
977
        session->participant_type = janus_videoroom_p_type_none;
978
        session->participant = NULL;
979
        session->destroyed = 0;
980
        g_atomic_int_set(&session->hangingup, 0);
981
        handle->plugin_handle = session;
982
        janus_mutex_lock(&sessions_mutex);
983
        g_hash_table_insert(sessions, handle, session);
984
        janus_mutex_unlock(&sessions_mutex);
985

    
986
        return;
987
}
988

    
989
void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
990
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
991
                *error = -1;
992
                return;
993
        }        
994
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle; 
995
        if(!session) {
996
                JANUS_LOG(LOG_ERR, "No VideoRoom session associated with this handle...\n");
997
                *error = -2;
998
                return;
999
        }
1000
        if(session->destroyed) {
1001
                JANUS_LOG(LOG_WARN, "VideoRoom session already marked as destroyed...\n");
1002
                return;
1003
        }
1004
        JANUS_LOG(LOG_VERB, "Removing VideoRoom session...\n");
1005
        /* Cleaning up and removing the session is done in a lazy way */
1006
        janus_mutex_lock(&sessions_mutex);
1007
        if(!session->destroyed) {
1008
                /* Any related WebRTC PeerConnection is not available anymore either */
1009
                janus_videoroom_hangup_media(handle);
1010
                session->destroyed = janus_get_monotonic_time();
1011
                old_sessions = g_list_append(old_sessions, session);
1012
                if(session->participant_type == janus_videoroom_p_type_publisher) {
1013
                        /* Get rid of publisher */
1014
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1015
                        participant->audio = FALSE;
1016
                        participant->video = FALSE;
1017
                        participant->data = FALSE;
1018
                        participant->audio_active = FALSE;
1019
                        participant->video_active = FALSE;
1020
                        participant->recording_active = FALSE;
1021
                        if(participant->recording_base)
1022
                                g_free(participant->recording_base);
1023
                        participant->recording_base = NULL;
1024
                        json_t *event = json_object();
1025
                        json_object_set_new(event, "videoroom", json_string("event"));
1026
                        json_object_set_new(event, "room", json_integer(participant->room->room_id));
1027
                        json_object_set_new(event, "leaving", json_integer(participant->user_id));
1028
                        char *leaving_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1029
                        json_decref(event);
1030
                        GHashTableIter iter;
1031
                        gpointer value;
1032
                        /* we need to check if the room still exists, may have been destroyed already */
1033
                        if(participant->room) {
1034
                                if(!participant->room->destroyed) {
1035
                                        janus_mutex_lock(&participant->room->participants_mutex);
1036
                                        g_hash_table_iter_init(&iter, participant->room->participants);
1037
                                        while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1038
                                                janus_videoroom_participant *p = value;
1039
                                                if(p == participant) {
1040
                                                        continue;        /* Skip the leaving publisher itself */
1041
                                                }
1042
                                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1043
                                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
1044
                                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1045
                                        }
1046
                                        g_hash_table_remove(participant->room->participants, GUINT_TO_POINTER(participant->user_id));
1047
                                        janus_mutex_unlock(&participant->room->participants_mutex);
1048
                                }
1049
                        }
1050
                        g_free(leaving_text);
1051
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1052
                        /* Detaching this listener from its publisher is already done by hangup_media */
1053
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1054
                        /* Detaching this listener from its publishers is already done by hangup_media */
1055
                }
1056
        }
1057
        janus_mutex_unlock(&sessions_mutex);
1058

    
1059
        return;
1060
}
1061

    
1062
char *janus_videoroom_query_session(janus_plugin_session *handle) {
1063
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1064
                return NULL;
1065
        }        
1066
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1067
        if(!session) {
1068
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1069
                return NULL;
1070
        }
1071
        /* Show the participant/room info, if any */
1072
        json_t *info = json_object();
1073
        if(session->participant) {
1074
                if(session->participant_type == janus_videoroom_p_type_none) {
1075
                        json_object_set_new(info, "type", json_string("none"));
1076
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
1077
                        json_object_set_new(info, "type", json_string("publisher"));
1078
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1079
                        if(participant) {
1080
                                janus_videoroom *room = participant->room; 
1081
                                json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1082
                                json_object_set_new(info, "id", json_integer(participant->user_id));
1083
                                if(participant->display)
1084
                                        json_object_set_new(info, "display", json_string(participant->display));
1085
                                if(participant->listeners)
1086
                                        json_object_set_new(info, "viewers", json_integer(g_slist_length(participant->listeners)));
1087
                                json_t *media = json_object();
1088
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1089
                                json_object_set_new(media, "video", json_integer(participant->video));
1090
                                json_object_set_new(media, "data", json_integer(participant->data));
1091
                                json_object_set_new(info, "media", media);
1092
                                if(participant->arc || participant->vrc) {
1093
                                        json_t *recording = json_object();
1094
                                        if(participant->arc && participant->arc->filename)
1095
                                                json_object_set_new(recording, "audio", json_string(participant->arc->filename));
1096
                                        if(participant->vrc && participant->vrc->filename)
1097
                                                json_object_set_new(recording, "video", json_string(participant->vrc->filename));
1098
                                        json_object_set_new(info, "recording", recording);
1099
                                }
1100
                        }
1101
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1102
                        json_object_set_new(info, "type", json_string("listener"));
1103
                        janus_videoroom_listener *participant = (janus_videoroom_listener *)session->participant;
1104
                        if(participant) {
1105
                                janus_videoroom_participant *feed = (janus_videoroom_participant *)participant->feed;
1106
                                if(feed) {
1107
                                        janus_videoroom *room = feed->room; 
1108
                                        json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1109
                                        json_object_set_new(info, "feed_id", json_integer(feed->user_id));
1110
                                        if(feed->display)
1111
                                                json_object_set_new(info, "feed_display", json_string(feed->display));
1112
                                }
1113
                                json_t *media = json_object();
1114
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1115
                                json_object_set_new(media, "video", json_integer(participant->video));
1116
                                json_object_set_new(media, "data", json_integer(participant->data));
1117
                                json_object_set_new(info, "media", media);
1118
                        }
1119
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1120
                        json_object_set_new(info, "type", json_string("muxed-listener"));
1121
                        /* TODO */
1122
                }
1123
        }
1124
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
1125
        char *info_text = json_dumps(info, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1126
        json_decref(info);
1127
        return info_text;
1128
}
1129

    
1130
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
1131
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1132
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized");
1133
        
1134
        /* Pre-parse the message */
1135
        int error_code = 0;
1136
        char error_cause[512];
1137
        json_t *root = NULL;
1138
        json_t *response = NULL;
1139

    
1140
        if(message == NULL) {
1141
                JANUS_LOG(LOG_ERR, "No message??\n");
1142
                error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
1143
                g_snprintf(error_cause, 512, "%s", "No message??");
1144
                goto error;
1145
        }
1146
        JANUS_LOG(LOG_VERB, "Handling message: %s\n", message);
1147

    
1148
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1149
        if(!session) {
1150
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1151
                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1152
                g_snprintf(error_cause, 512, "%s", "session associated with this handle...");
1153
                goto error;
1154
        }
1155
        if(session->destroyed) {
1156
                JANUS_LOG(LOG_ERR, "Session has already been marked as destroyed...\n");
1157
                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1158
                g_snprintf(error_cause, 512, "%s", "Session has already been marked as destroyed...");
1159
                goto error;
1160
        }
1161
        json_error_t error;
1162
        root = json_loads(message, 0, &error);
1163
        if(!root) {
1164
                JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
1165
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
1166
                g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text);
1167
                goto error;
1168
        }
1169
        if(!json_is_object(root)) {
1170
                JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1171
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
1172
                g_snprintf(error_cause, 512, "JSON error: not an object");
1173
                goto error;
1174
        }
1175
        /* Get the request first */
1176
        JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
1177
                error_code, error_cause, TRUE,
1178
                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1179
        if(error_code != 0)
1180
                goto error;
1181
        json_t *request = json_object_get(root, "request");
1182
        /* Some requests ('create', 'destroy', 'exists', 'list') can be handled synchronously */
1183
        const char *request_text = json_string_value(request);
1184
        if(!strcasecmp(request_text, "create")) {
1185
                /* Create a new videoroom */
1186
                JANUS_LOG(LOG_VERB, "Creating a new videoroom\n");
1187
                JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
1188
                        error_code, error_cause, TRUE,
1189
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1190
                if(error_code != 0)
1191
                        goto error;
1192
                json_t *desc = json_object_get(root, "description");
1193
                json_t *is_private = json_object_get(root, "is_private");
1194
                json_t *secret = json_object_get(root, "secret");
1195
                json_t *pin = json_object_get(root, "pin");
1196
                json_t *bitrate = json_object_get(root, "bitrate");
1197
                json_t *fir_freq = json_object_get(root, "fir_freq");
1198
                json_t *publishers = json_object_get(root, "publishers");
1199
                json_t *audiocodec = json_object_get(root, "audiocodec");
1200
                if(audiocodec) {
1201
                        const char *audiocodec_value = json_string_value(audiocodec);
1202
                        if(!strcasecmp(audiocodec_value, "opus") && !strcasecmp(audiocodec_value, "isac32") && !strcasecmp(audiocodec_value, "isac16") && !strcasecmp(audiocodec_value, "pcmu") && !strcasecmp(audiocodec_value, "pcma")) {
1203
                                JANUS_LOG(LOG_ERR, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, or pcma)\n");
1204
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1205
                                g_snprintf(error_cause, 512, "Invalid element (audiocodec can only be opus, isac32, isac16, pcmu, or pcma)");
1206
                                goto error;
1207
                        }
1208
                }
1209
                json_t *videocodec = json_object_get(root, "videocodec");
1210
                if(videocodec) {
1211
                        const char *videocodec_value = json_string_value(videocodec);
1212
                        if(!strcasecmp(videocodec_value, "vp8") && !strcasecmp(videocodec_value, "vp9") && !strcasecmp(videocodec_value, "h264")) {
1213
                                JANUS_LOG(LOG_ERR, "Invalid element (videocodec can only be vp8, vp9 or h264)\n");
1214
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1215
                                g_snprintf(error_cause, 512, "Invalid element (videocodec can only be vp8, vp9 or h264)");
1216
                                goto error;
1217
                        }
1218
                }
1219
                json_t *record = json_object_get(root, "record");
1220
                json_t *rec_dir = json_object_get(root, "rec_dir");
1221
                json_t *permanent = json_object_get(root, "permanent");
1222
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1223
                if(save && config == NULL) {
1224
                        JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
1225
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1226
                        g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
1227
                        goto error;
1228
                }
1229
                guint64 room_id = 0;
1230
                json_t *room = json_object_get(root, "room");
1231
                if(room) {
1232
                        room_id = json_integer_value(room);
1233
                        if(room_id == 0) {
1234
                                JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
1235
                        }
1236
                }
1237
                janus_mutex_lock(&rooms_mutex);
1238
                if(room_id > 0) {
1239
                        /* Let's make sure the room doesn't exist already */
1240
                        if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
1241
                                /* It does... */
1242
                                janus_mutex_unlock(&rooms_mutex);
1243
                                JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
1244
                                error_code = JANUS_VIDEOROOM_ERROR_ROOM_EXISTS;
1245
                                g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
1246
                                goto error;
1247
                        }
1248
                }
1249
                /* Create the room */
1250
                janus_videoroom *videoroom = g_malloc0(sizeof(janus_videoroom));
1251
                if(videoroom == NULL) {
1252
                        janus_mutex_unlock(&rooms_mutex);
1253
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
1254
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1255
                        g_snprintf(error_cause, 512, "Memory error");
1256
                        goto error;
1257
                }
1258
                /* Generate a random ID */
1259
                if(room_id == 0) {
1260
                        while(room_id == 0) {
1261
                                room_id = g_random_int();
1262
                                if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
1263
                                        /* Room ID already taken, try another one */
1264
                                        room_id = 0;
1265
                                }
1266
                        }
1267
                }
1268
                videoroom->room_id = room_id;
1269
                char *description = NULL;
1270
                if(desc != NULL && strlen(json_string_value(desc)) > 0) {
1271
                        description = g_strdup(json_string_value(desc));
1272
                } else {
1273
                        char roomname[255];
1274
                        g_snprintf(roomname, 255, "Room %"SCNu64"", videoroom->room_id);
1275
                        description = g_strdup(roomname);
1276
                }
1277
                if(description == NULL) {
1278
                        janus_mutex_unlock(&rooms_mutex);
1279
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
1280
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1281
                        g_snprintf(error_cause, 512, "Memory error");
1282
                        goto error;
1283
                }
1284
                videoroom->room_name = description;
1285
                videoroom->is_private = is_private ? json_is_true(is_private) : FALSE;
1286
                if(secret)
1287
                        videoroom->room_secret = g_strdup(json_string_value(secret));
1288
                if(pin)
1289
                        videoroom->room_pin = g_strdup(json_string_value(pin));
1290
                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
1291
                if(publishers)
1292
                        videoroom->max_publishers = json_integer_value(publishers);
1293
                if(videoroom->max_publishers < 0)
1294
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
1295
                videoroom->bitrate = 0;
1296
                if(bitrate)
1297
                        videoroom->bitrate = json_integer_value(bitrate);
1298
                if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
1299
                        videoroom->bitrate = 64000;        /* Don't go below 64k */
1300
                videoroom->fir_freq = 0;
1301
                if(fir_freq)
1302
                        videoroom->fir_freq = json_integer_value(fir_freq);
1303
                videoroom->acodec = JANUS_VIDEOROOM_OPUS;
1304
                if(audiocodec) {
1305
                        const char *audiocodec_value = json_string_value(audiocodec);
1306
                        if(!strcasecmp(audiocodec_value, "opus"))
1307
                                videoroom->acodec = JANUS_VIDEOROOM_OPUS;
1308
                        else if(!strcasecmp(audiocodec_value, "isac32"))
1309
                                videoroom->acodec = JANUS_VIDEOROOM_ISAC_32K;
1310
                        else if(!strcasecmp(audiocodec_value, "isac16"))
1311
                                videoroom->acodec = JANUS_VIDEOROOM_ISAC_16K;
1312
                        else if(!strcasecmp(audiocodec_value, "pcmu"))
1313
                                videoroom->acodec = JANUS_VIDEOROOM_PCMU;
1314
                        else if(!strcasecmp(audiocodec_value, "pcma"))
1315
                                videoroom->acodec = JANUS_VIDEOROOM_PCMA;
1316
                        else {
1317
                                JANUS_LOG(LOG_WARN, "Unsupported audio codec '%s', falling back to OPUS\n", audiocodec_value);
1318
                                videoroom->acodec = JANUS_VIDEOROOM_OPUS;
1319
                        }
1320
                }
1321
                videoroom->vcodec = JANUS_VIDEOROOM_VP8;
1322
                if(videocodec) {
1323
                        const char *videocodec_value = json_string_value(videocodec);
1324
                        if(!strcasecmp(videocodec_value, "vp8"))
1325
                                videoroom->vcodec = JANUS_VIDEOROOM_VP8;
1326
                        else if(!strcasecmp(videocodec_value, "vp9"))
1327
                                videoroom->vcodec = JANUS_VIDEOROOM_VP9;
1328
                        else if(!strcasecmp(videocodec_value, "h264"))
1329
                                videoroom->vcodec = JANUS_VIDEOROOM_H264;
1330
                        else {
1331
                                JANUS_LOG(LOG_WARN, "Unsupported video codec '%s', falling back to VP8\n", videocodec_value);
1332
                                videoroom->vcodec = JANUS_VIDEOROOM_VP8;
1333
                        }
1334
                }
1335
                if(record) {
1336
                        videoroom->record = json_is_true(record);
1337
                }
1338
                if(rec_dir) {
1339
                        videoroom->rec_dir = g_strdup(json_string_value(rec_dir));
1340
                }
1341
                videoroom->destroyed = 0;
1342
                janus_mutex_init(&videoroom->participants_mutex);
1343
                videoroom->participants = g_hash_table_new(NULL, NULL);
1344
                JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, %s/%s codecs, secret: %s, pin: %s)\n",
1345
                        videoroom->room_id, videoroom->room_name,
1346
                        videoroom->is_private ? "private" : "public",
1347
                        janus_videoroom_audiocodec_name(videoroom->acodec),
1348
                        janus_videoroom_videocodec_name(videoroom->vcodec),
1349
                        videoroom->room_secret ? videoroom->room_secret : "no secret",
1350
                        videoroom->room_pin ? videoroom->room_pin : "no pin");
1351
                if(videoroom->record) {
1352
                        JANUS_LOG(LOG_VERB, "  -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
1353
                }
1354
                if(save) {
1355
                        /* This room is permanent: save to the configuration file too
1356
                         * FIXME: We should check if anything fails... */
1357
                        JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", videoroom->room_id);
1358
                        janus_mutex_lock(&config_mutex);
1359
                        char cat[BUFSIZ], value[BUFSIZ];
1360
                        /* The room ID is the category */
1361
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, videoroom->room_id);
1362
                        janus_config_add_category(config, cat);
1363
                        /* Now for the values */
1364
                        janus_config_add_item(config, cat, "description", videoroom->room_name);
1365
                        if(videoroom->is_private)
1366
                                janus_config_add_item(config, cat, "is_private", "yes");
1367
                        g_snprintf(value, BUFSIZ, "%"SCNu64, videoroom->bitrate);
1368
                        janus_config_add_item(config, cat, "bitrate", value);
1369
                        g_snprintf(value, BUFSIZ, "%d", videoroom->max_publishers);
1370
                        janus_config_add_item(config, cat, "publishers", value);
1371
                        if(videoroom->fir_freq) {
1372
                                g_snprintf(value, BUFSIZ, "%"SCNu16, videoroom->fir_freq);
1373
                                janus_config_add_item(config, cat, "fir_freq", value);
1374
                        }
1375
                        janus_config_add_item(config, cat, "audiocodec", janus_videoroom_audiocodec_name(videoroom->acodec));
1376
                        janus_config_add_item(config, cat, "videocodec", janus_videoroom_videocodec_name(videoroom->vcodec));
1377
                        if(videoroom->room_secret)
1378
                                janus_config_add_item(config, cat, "secret", videoroom->room_secret);
1379
                        if(videoroom->room_pin)
1380
                                janus_config_add_item(config, cat, "pin", videoroom->room_pin);
1381
                        if(videoroom->record)
1382
                                janus_config_add_item(config, cat, "record", "yes");
1383
                        if(videoroom->rec_dir)
1384
                                janus_config_add_item(config, cat, "rec_dir", videoroom->rec_dir);
1385
                        /* Save modified configuration */
1386
                        janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE);
1387
                        janus_mutex_unlock(&config_mutex);
1388
                }
1389
                /* Show updated rooms list */
1390
                GHashTableIter iter;
1391
                gpointer value;
1392
                g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
1393
                g_hash_table_iter_init(&iter, rooms);
1394
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1395
                        janus_videoroom *vr = value;
1396
                        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);
1397
                }
1398
                janus_mutex_unlock(&rooms_mutex);
1399
                /* Send info back */
1400
                response = json_object();
1401
                json_object_set_new(response, "videoroom", json_string("created"));
1402
                json_object_set_new(response, "room", json_integer(videoroom->room_id));
1403
                goto plugin_response;
1404
        } else if(!strcasecmp(request_text, "destroy")) {
1405
                JANUS_LOG(LOG_VERB, "Attempt to destroy an existing videoroom room\n");
1406
                JANUS_VALIDATE_JSON_OBJECT(root, destroy_parameters,
1407
                        error_code, error_cause, TRUE,
1408
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1409
                if(error_code != 0)
1410
                        goto error;
1411
                json_t *room = json_object_get(root, "room");
1412
                json_t *permanent = json_object_get(root, "permanent");
1413
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1414
                if(save && config == NULL) {
1415
                        JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
1416
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1417
                        g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
1418
                        goto error;
1419
                }
1420
                guint64 room_id = json_integer_value(room);
1421
                janus_mutex_lock(&rooms_mutex);
1422
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1423
                if(videoroom == NULL) {
1424
                        janus_mutex_unlock(&rooms_mutex);
1425
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1426
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1427
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1428
                        goto error;
1429
                }
1430
                
1431
                if(videoroom->destroyed) {
1432
                        janus_mutex_unlock(&rooms_mutex)
1433
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", videoroom->room_id);
1434
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1435
                        g_snprintf(error_cause, 512, "Videoroom (%"SCNu64")", videoroom->room_id);
1436
                        goto error;
1437
                }
1438
                /* A secret may be required for this action */
1439
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1440
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1441
                if(error_code != 0) {
1442
                        janus_mutex_unlock(&rooms_mutex);
1443
                        goto error;
1444
                }
1445
                /* Notify all participants that the fun is over, and that they'll be kicked */
1446
                JANUS_LOG(LOG_VERB, "Notifying all participants\n");
1447
                json_t *destroyed = json_object();
1448
                json_object_set_new(destroyed, "videoroom", json_string("destroyed"));
1449
                json_object_set_new(destroyed, "room", json_integer(videoroom->room_id));
1450
                char *destroyed_text = json_dumps(destroyed, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1451
                GHashTableIter iter;
1452
                gpointer value;
1453
                /* Remove room lazily*/
1454
                videoroom->destroyed = janus_get_monotonic_time();
1455
                old_rooms = g_list_append(old_rooms, videoroom);
1456
                janus_mutex_lock(&videoroom->participants_mutex);
1457
                g_hash_table_iter_init(&iter, videoroom->participants);
1458
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1459
                        janus_videoroom_participant *p = value;
1460
                        if(p && p->session) {
1461
                                /* Notify the user we're going to destroy the room... */
1462
                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, destroyed_text, NULL, NULL);
1463
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1464
                                /* ... and then ask the core to remove the handle */
1465
                                gateway->end_session(p->session->handle);
1466
                        }
1467
                }
1468
                json_decref(destroyed);
1469
                g_free(destroyed_text);
1470
                janus_mutex_unlock(&videoroom->participants_mutex);
1471
                janus_mutex_unlock(&rooms_mutex);
1472
                if(save) {
1473
                        /* This change is permanent: save to the configuration file too
1474
                         * FIXME: We should check if anything fails... */
1475
                        JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id);
1476
                        janus_mutex_lock(&config_mutex);
1477
                        char cat[BUFSIZ];
1478
                        /* The room ID is the category */
1479
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, room_id);
1480
                        janus_config_remove_category(config, cat);
1481
                        /* Save modified configuration */
1482
                        janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE);
1483
                        janus_mutex_unlock(&config_mutex);
1484
                }
1485
                /* Done */
1486
                response = json_object();
1487
                json_object_set_new(response, "videoroom", json_string("destroyed"));
1488
                json_object_set_new(response, "room", json_integer(room_id));
1489
                goto plugin_response;
1490
        } else if(!strcasecmp(request_text, "list")) {
1491
                /* List all rooms (but private ones) and their details (except for the secret, of course...) */
1492
                json_t *list = json_array();
1493
                JANUS_LOG(LOG_VERB, "Getting the list of video rooms\n");
1494
                janus_mutex_lock(&rooms_mutex);
1495
                GHashTableIter iter;
1496
                gpointer value;
1497
                g_hash_table_iter_init(&iter, rooms);
1498
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1499
                        janus_videoroom *room = value;
1500
                        if(!room)
1501
                                continue;
1502
                        if(room->is_private) {
1503
                                /* Skip private room */
1504
                                JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
1505
                                continue;
1506
                        }
1507
                        if(!room->destroyed) {
1508
                                json_t *rl = json_object();
1509
                                json_object_set_new(rl, "room", json_integer(room->room_id));
1510
                                json_object_set_new(rl, "description", json_string(room->room_name));
1511
                                json_object_set_new(rl, "max_publishers", json_integer(room->max_publishers));
1512
                                json_object_set_new(rl, "bitrate", json_integer(room->bitrate));
1513
                                json_object_set_new(rl, "fir_freq", json_integer(room->fir_freq));
1514
                                json_object_set_new(rl, "audiocodec", json_string(janus_videoroom_audiocodec_name(room->acodec)));
1515
                                json_object_set_new(rl, "videocodec", json_string(janus_videoroom_videocodec_name(room->vcodec)));
1516
                                json_object_set_new(rl, "record", json_string(room->record ? "true" : "false"));
1517
                                json_object_set_new(rl, "rec_dir", json_string(room->rec_dir));
1518
                                /* TODO: Should we list participants as well? or should there be a separate API call on a specific room for this? */
1519
                                json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
1520
                                json_array_append_new(list, rl);
1521
                        }
1522
                }
1523
                janus_mutex_unlock(&rooms_mutex);
1524
                response = json_object();
1525
                json_object_set_new(response, "videoroom", json_string("success"));
1526
                json_object_set_new(response, "list", list);
1527
                goto plugin_response;
1528
        } else if(!strcasecmp(request_text, "rtp_forward")) {
1529
                JANUS_VALIDATE_JSON_OBJECT(root, rtp_forward_parameters,
1530
                        error_code, error_cause, TRUE,
1531
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1532
                if(error_code != 0)
1533
                        goto error;
1534
                json_t *room = json_object_get(root, "room");
1535
                json_t *pub_id = json_object_get(root, "publisher_id");
1536
                int video_port = -1;
1537
                int audio_port = -1;
1538
                json_t *vid_port = json_object_get(root, "video_port");
1539
                if(vid_port) {
1540
                        video_port = json_integer_value(vid_port);
1541
                }
1542
                json_t *au_port = json_object_get(root, "audio_port");
1543
                if(au_port) {
1544
                        audio_port = json_integer_value(au_port);
1545
                }
1546
                json_t *json_host = json_object_get(root, "host");
1547
                
1548
                guint64 room_id = json_integer_value(room);
1549
                guint64 publisher_id = json_integer_value(pub_id);
1550
                const gchar* host = json_string_value(json_host);
1551
                janus_mutex_lock(&rooms_mutex);
1552
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1553
                if(videoroom == NULL) {
1554
                        janus_mutex_unlock(&rooms_mutex);
1555
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1556
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1557
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1558
                        goto error;
1559
                }
1560
                if(videoroom->destroyed) {
1561
                        janus_mutex_unlock(&rooms_mutex)
1562
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", videoroom->room_id);
1563
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1564
                        g_snprintf(error_cause, 512, "Videoroom (%"SCNu64")", videoroom->room_id);
1565
                        goto error;
1566
                }
1567
                /* A secret may be required for this action */
1568
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1569
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1570
                if(error_code != 0) {
1571
                        janus_mutex_unlock(&rooms_mutex);
1572
                        goto error;
1573
                }
1574
                janus_mutex_unlock(&rooms_mutex);
1575
                janus_mutex_lock(&videoroom->participants_mutex);
1576
                janus_videoroom_participant* publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(publisher_id));
1577
                if(publisher == NULL) {
1578
                        janus_mutex_unlock(&videoroom->participants_mutex);
1579
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1580
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1581
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1582
                        goto error;
1583
                }
1584
                if(publisher->udp_sock <= 0) {
1585
                        publisher->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
1586
                        if(publisher->udp_sock <= 0) {
1587
                                janus_mutex_unlock(&videoroom->participants_mutex);
1588
                                JANUS_LOG(LOG_ERR, "Could not open UDP socket for rtp stream for publisher (%"SCNu64")\n", publisher_id);
1589
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1590
                                g_snprintf(error_cause, 512, "Could not open UDP socket for rtp stream");
1591
                                goto error;
1592
                        }
1593
                }
1594
                guint32 audio_handle = 0;
1595
                guint32 video_handle = 0;
1596
                if(audio_port > 0) {
1597
                        audio_handle = janus_rtp_forwarder_add_helper(publisher, host, audio_port, 0);
1598
                }
1599
                if(video_port > 0) {
1600
                        video_handle = janus_rtp_forwarder_add_helper(publisher, host, video_port, 1);
1601
                }
1602
                janus_mutex_unlock(&videoroom->participants_mutex);
1603
                response = json_object();
1604
                json_t* rtp_stream = json_object();
1605
                if(audio_handle > 0) {
1606
                        json_object_set_new(rtp_stream, "audio_stream_id", json_integer(audio_handle));
1607
                        json_object_set_new(rtp_stream, "audio", json_integer(audio_port));
1608
                }
1609
                if(video_handle > 0) {
1610
                        /* Send a FIR to the new RTP forward publisher */
1611
                        char buf[20];
1612
                        memset(buf, 0, 20);
1613
                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
1614
                        JANUS_LOG(LOG_VERB, "New RTP forward publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
1615
                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
1616
                        /* Send a PLI too, just in case... */
1617
                        memset(buf, 0, 12);
1618
                        janus_rtcp_pli((char *)&buf, 12);
1619
                        JANUS_LOG(LOG_VERB, "New RTP forward publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
1620
                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
1621
                        /* Done */
1622
                        json_object_set_new(rtp_stream, "video_stream_id", json_integer(video_handle));
1623
                        json_object_set_new(rtp_stream, "video", json_integer(video_port));
1624
                }
1625
                json_object_set_new(rtp_stream, "host", json_string(host));
1626
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1627
                json_object_set_new(response, "rtp_stream", rtp_stream);
1628
                json_object_set_new(response, "room", json_integer(room_id));
1629
                json_object_set_new(response, "videoroom", json_string("rtp_forward"));
1630
                goto plugin_response;
1631
        } else if(!strcasecmp(request_text, "stop_rtp_forward")) {
1632
                JANUS_VALIDATE_JSON_OBJECT(root, stop_rtp_forward_parameters,
1633
                        error_code, error_cause, TRUE,
1634
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1635
                if(error_code != 0)
1636
                        goto error;
1637
                json_t *room = json_object_get(root, "room");
1638
                json_t *pub_id = json_object_get(root, "publisher_id");
1639
                json_t *id = json_object_get(root, "stream_id");
1640

    
1641
                guint64 room_id = json_integer_value(room);
1642
                guint64 publisher_id = json_integer_value(pub_id);
1643
                guint32 stream_id = json_integer_value(id);
1644
                janus_mutex_lock(&rooms_mutex);
1645
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1646
                if(videoroom == NULL) {
1647
                        janus_mutex_unlock(&rooms_mutex);
1648
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1649
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1650
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1651
                        goto error;
1652
                }
1653
                if(videoroom->destroyed) {
1654
                        janus_mutex_unlock(&rooms_mutex)
1655
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", videoroom->room_id);
1656
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1657
                        g_snprintf(error_cause, 512, "Videoroom (%"SCNu64")", videoroom->room_id);
1658
                        goto error;
1659
                }
1660
                /* A secret may be required for this action */
1661
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1662
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1663
                if(error_code != 0) {
1664
                        janus_mutex_unlock(&rooms_mutex);
1665
                        goto error;
1666
                }
1667
                janus_mutex_unlock(&rooms_mutex);
1668

    
1669
                janus_mutex_lock(&videoroom->participants_mutex);
1670
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(publisher_id));
1671
                if(publisher == NULL) {
1672
                        janus_mutex_unlock(&videoroom->participants_mutex);
1673
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1674
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1675
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1676
                        goto error;
1677
                }
1678
                janus_mutex_lock(&publisher->rtp_forwarders_mutex);
1679
                if(g_hash_table_lookup(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id)) == NULL) {
1680
                        janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1681
                        janus_mutex_unlock(&videoroom->participants_mutex);
1682
                        JANUS_LOG(LOG_ERR, "No such stream (%"SCNu32")\n", stream_id);
1683
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1684
                        g_snprintf(error_cause, 512, "No such stream (%"SCNu32")", stream_id);
1685
                        goto error;
1686
                }
1687
                g_hash_table_remove(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id));
1688
                janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1689
                janus_mutex_unlock(&videoroom->participants_mutex);
1690
                response = json_object();
1691
                json_object_set_new(response, "videoroom", json_string("stop_rtp_forward"));
1692
                json_object_set_new(response, "room", json_integer(room_id));
1693
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1694
                json_object_set_new(response, "stream_id", json_integer(stream_id));
1695
                goto plugin_response;
1696
        } else if(!strcasecmp(request_text, "exists")) {
1697
                /* Check whether a given room exists or not, returns true/false */        
1698
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1699
                        error_code, error_cause, TRUE,
1700
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1701
                if(error_code != 0)
1702
                        goto error;
1703
                json_t *room = json_object_get(root, "room");
1704
                guint64 room_id = json_integer_value(room);
1705
                janus_mutex_lock(&rooms_mutex);
1706
                gboolean room_exists = g_hash_table_contains(rooms, GUINT_TO_POINTER(room_id));
1707
                janus_mutex_unlock(&rooms_mutex);
1708
                response = json_object();
1709
                json_object_set_new(response, "videoroom", json_string("success"));
1710
                json_object_set_new(response, "room", json_integer(room_id));
1711
                json_object_set_new(response, "exists", json_string(room_exists ? "true" : "false"));
1712
                goto plugin_response;
1713
        } else if(!strcasecmp(request_text, "listparticipants")) {
1714
                /* List all participants in a room, specifying whether they're publishers or just attendees */        
1715
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1716
                        error_code, error_cause, TRUE,
1717
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1718
                if(error_code != 0)
1719
                        goto error;
1720
                json_t *room = json_object_get(root, "room");
1721
                guint64 room_id = json_integer_value(room);
1722
                janus_mutex_lock(&rooms_mutex);
1723
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1724
                janus_mutex_unlock(&rooms_mutex);
1725
                if(videoroom == NULL) {
1726
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1727
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1728
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1729
                        goto error;
1730
                }
1731
                if(videoroom->destroyed) {
1732
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1733
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1734
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1735
                        goto error;
1736
                }
1737
                /* Return a list of all participants (whether they're publishing or not) */
1738
                json_t *list = json_array();
1739
                GHashTableIter iter;
1740
                gpointer value;
1741
                janus_mutex_lock(&videoroom->participants_mutex);
1742
                g_hash_table_iter_init(&iter, videoroom->participants);
1743
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1744
                        janus_videoroom_participant *p = value;
1745
                        json_t *pl = json_object();
1746
                        json_object_set_new(pl, "id", json_integer(p->user_id));
1747
                        if(p->display)
1748
                                json_object_set_new(pl, "display", json_string(p->display));
1749
                        json_object_set_new(pl, "publisher", json_string(p->sdp ? "true" : "false"));
1750
                        json_array_append_new(list, pl);
1751
                }
1752
                janus_mutex_unlock(&videoroom->participants_mutex);
1753
                response = json_object();
1754
                json_object_set_new(response, "videoroom", json_string("participants"));
1755
                json_object_set_new(response, "room", json_integer(room_id));
1756
                json_object_set_new(response, "participants", list);
1757
                goto plugin_response;
1758
        } else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")
1759
                        || !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish") || !strcasecmp(request_text, "unpublish")
1760
                        || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "switch") || !strcasecmp(request_text, "stop")
1761
                        || !strcasecmp(request_text, "add") || !strcasecmp(request_text, "remove") || !strcasecmp(request_text, "leave")) {
1762
                /* These messages are handled asynchronously */
1763

    
1764
                janus_videoroom_message *msg = g_malloc0(sizeof(janus_videoroom_message));
1765
                if(msg == NULL) {
1766
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
1767
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1768
                        g_snprintf(error_cause, 512, "Memory error");
1769
                        goto error;
1770
                }
1771

    
1772
                g_free(message);
1773
                msg->handle = handle;
1774
                msg->transaction = transaction;
1775
                msg->message = root;
1776
                msg->sdp_type = sdp_type;
1777
                msg->sdp = sdp;
1778
                g_async_queue_push(messages, msg);
1779

    
1780
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL);
1781
        } else {
1782
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1783
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
1784
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1785
                goto error;
1786
        }
1787

    
1788
plugin_response:
1789
                {
1790
                        if (!response) {
1791
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1792
                                g_snprintf(error_cause, 512, "Invalid response");
1793
                                goto error;
1794
                        }
1795
                        if(root != NULL)
1796
                                json_decref(root);
1797
                        g_free(transaction);
1798
                        g_free(message);
1799
                        g_free(sdp_type);
1800
                        g_free(sdp);
1801

    
1802
                        char *response_text = json_dumps(response, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1803
                        json_decref(response);
1804
                        janus_plugin_result *result = janus_plugin_result_new(JANUS_PLUGIN_OK, response_text);
1805
                        g_free(response_text);
1806
                        return result;
1807
                }
1808

    
1809
error:
1810
                {
1811
                        if(root != NULL)
1812
                                json_decref(root);
1813
                        g_free(transaction);
1814
                        g_free(message);
1815
                        g_free(sdp_type);
1816
                        g_free(sdp);
1817

    
1818
                        /* Prepare JSON error event */
1819
                        json_t *event = json_object();
1820
                        json_object_set_new(event, "videoroom", json_string("event"));
1821
                        json_object_set_new(event, "error_code", json_integer(error_code));
1822
                        json_object_set_new(event, "error", json_string(error_cause));
1823
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1824
                        json_decref(event);
1825
                        janus_plugin_result *result = janus_plugin_result_new(JANUS_PLUGIN_OK, event_text);
1826
                        g_free(event_text);
1827
                        return result;
1828
                }
1829

    
1830
}
1831

    
1832
void janus_videoroom_setup_media(janus_plugin_session *handle) {
1833
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
1834
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1835
                return;
1836
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1837
        if(!session) {
1838
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1839
                return;
1840
        }
1841
        if(session->destroyed)
1842
                return;
1843
        g_atomic_int_set(&session->hangingup, 0);
1844
        /* Media relaying can start now */
1845
        session->started = TRUE;
1846
        /* If this is a listener, ask the publisher a FIR */
1847
        if(session->participant) {
1848
                if(session->participant_type == janus_videoroom_p_type_subscriber) {
1849
                        janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
1850
                        if(l && l->feed) {
1851
                                janus_videoroom_participant *p = l->feed;
1852
                                if(p && p->session) {
1853
                                        /* Send a FIR */
1854
                                        char buf[20];
1855
                                        memset(buf, 0, 20);
1856
                                        janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
1857
                                        JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1858
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 20);
1859
                                        /* Send a PLI too, just in case... */
1860
                                        memset(buf, 0, 12);
1861
                                        janus_rtcp_pli((char *)&buf, 12);
1862
                                        JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1863
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 12);
1864
                                }
1865
                        }
1866
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1867
                        /* Do the same, but for all feeds */
1868
                        janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
1869
                        if(listener == NULL)
1870
                                return;
1871
                        GSList *ps = listener->listeners;
1872
                        while(ps) {
1873
                                janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
1874
                                if(l && l->feed) {
1875
                                        janus_videoroom_participant *p = l->feed;
1876
                                        if(p && p->session) {
1877
                                                /* Send a FIR */
1878
                                                char buf[20];
1879
                                                memset(buf, 0, 20);
1880
                                                janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
1881
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1882
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
1883
                                                /* Send a PLI too, just in case... */
1884
                                                memset(buf, 0, 12);
1885
                                                janus_rtcp_pli((char *)&buf, 12);
1886
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1887
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 12);
1888
                                        }
1889
                                }
1890
                                ps = ps->next;
1891
                        }
1892
                }
1893
        }
1894
}
1895

    
1896
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
1897
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
1898
                return;
1899
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1900
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
1901
                return;
1902
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1903
        if((!video && participant->audio_active) || (video && participant->video_active)) {
1904
                /* Update payload type and SSRC */
1905
                rtp_header *rtp = (rtp_header *)buf;
1906
                rtp->type = video ? participant->video_pt : participant->audio_pt;
1907
                rtp->ssrc = htonl(video ? participant->video_ssrc : participant->audio_ssrc);
1908
                /* Forward RTP to the appropriate port for the rtp_forwarders associated wih this publisher, if there are any */
1909
                GHashTableIter iter;
1910
                gpointer value;
1911
                g_hash_table_iter_init(&iter, participant->rtp_forwarders);
1912
                janus_mutex_lock(&participant->rtp_forwarders_mutex);
1913
                while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
1914
                        rtp_forwarder* rtp_forward = (rtp_forwarder*)value;
1915
                        if(video && rtp_forward->is_video) {
1916
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
1917
                        }
1918
                        else if(!video && !rtp_forward->is_video) {
1919
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
1920
                        }
1921
                }
1922
                janus_mutex_unlock(&participant->rtp_forwarders_mutex);
1923
                /* Save the frame if we're recording */
1924
                if(video && participant->vrc)
1925
                        janus_recorder_save_frame(participant->vrc, buf, len);
1926
                else if(!video && participant->arc)
1927
                        janus_recorder_save_frame(participant->arc, buf, len);
1928
                /* Done, relay it */
1929
                janus_videoroom_rtp_relay_packet packet;
1930
                packet.data = rtp;
1931
                packet.length = len;
1932
                packet.is_video = video;
1933
                /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */
1934
                packet.timestamp = ntohl(packet.data->timestamp);
1935
                packet.seq_number = ntohs(packet.data->seq_number);
1936
                /* Go */
1937
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
1938
                
1939
                /* Check if we need to send any REMB, FIR or PLI back to this publisher */
1940
                if(video && participant->video_active) {
1941
                        /* Did we send a REMB already, or is it time to send one? */
1942
                        gboolean send_remb = FALSE;
1943
                        if(participant->remb_latest == 0 && participant->remb_startup > 0) {
1944
                                /* Still in the starting phase, send the ramp-up REMB feedback */
1945
                                send_remb = TRUE;
1946
                        } else if(participant->remb_latest > 0 && janus_get_monotonic_time()-participant->remb_latest >= 5*G_USEC_PER_SEC) {
1947
                                /* 5 seconds have passed since the last REMB, send a new one */
1948
                                send_remb = TRUE;
1949
                        }                
1950
                        if(send_remb) {
1951
                                /* We send a few incremental REMB messages at startup */
1952
                                uint64_t bitrate = (participant->bitrate ? participant->bitrate : 256*1024);
1953
                                if(participant->remb_startup > 0) {
1954
                                        bitrate = bitrate/participant->remb_startup;
1955
                                        participant->remb_startup--;
1956
                                }
1957
                                JANUS_LOG(LOG_VERB, "Sending REMB\n");
1958
                                char rtcpbuf[24];
1959
                                janus_rtcp_remb((char *)(&rtcpbuf), 24, bitrate);
1960
                                gateway->relay_rtcp(handle, video, rtcpbuf, 24);
1961
                                if(participant->remb_startup == 0)
1962
                                        participant->remb_latest = janus_get_monotonic_time();
1963
                        }
1964
                        /* Generate FIR/PLI too, if needed */
1965
                        if(video && participant->video_active && (participant->room->fir_freq > 0)) {
1966
                                /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
1967
                                gint64 now = janus_get_monotonic_time();
1968
                                if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
1969
                                        /* FIXME We send a FIR every tot seconds */
1970
                                        participant->fir_latest = now;
1971
                                        char rtcpbuf[24];
1972
                                        memset(rtcpbuf, 0, 24);
1973
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &participant->fir_seq);
1974
                                        JANUS_LOG(LOG_VERB, "Sending FIR to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
1975
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 20);
1976
                                        /* Send a PLI too, just in case... */
1977
                                        memset(rtcpbuf, 0, 12);
1978
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
1979
                                        JANUS_LOG(LOG_VERB, "Sending PLI to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
1980
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 12);
1981
                                }
1982
                        }
1983
                }
1984
        }
1985
}
1986

    
1987
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
1988
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1989
                return;
1990
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1991
        if(!session) {
1992
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1993
                return;
1994
        }
1995
        if(session->destroyed)
1996
                return;
1997
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
1998
                /* A listener sent some RTCP, check what it is and if we need to forward it to the publisher */
1999
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
2000
                if(!l->video)
2001
                        return;        /* The only feedback we handle is video related anyway... */
2002
                if(janus_rtcp_has_fir(buf, len)) {
2003
                        /* We got a FIR, forward it to the publisher */
2004
                        if(l && l->feed) {
2005
                                janus_videoroom_participant *p = l->feed;
2006
                                if(p && p->session) {
2007
                                        char rtcpbuf[20];
2008
                                        memset(rtcpbuf, 0, 20);
2009
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &p->fir_seq);
2010
                                        JANUS_LOG(LOG_VERB, "Got a FIR from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2011
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 20);
2012
                                }
2013
                        }
2014
                }
2015
                if(janus_rtcp_has_pli(buf, len)) {
2016
                        /* We got a PLI, forward it to the publisher */
2017
                        if(l && l->feed) {
2018
                                janus_videoroom_participant *p = l->feed;
2019
                                if(p && p->session) {
2020
                                        char rtcpbuf[12];
2021
                                        memset(rtcpbuf, 0, 12);
2022
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2023
                                        JANUS_LOG(LOG_VERB, "Got a PLI from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2024
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 12);
2025
                                }
2026
                        }
2027
                }
2028
                uint64_t bitrate = janus_rtcp_get_remb(buf, len);
2029
                if(bitrate > 0) {
2030
                        /* FIXME We got a REMB from this listener, should we do something about it? */
2031
                }
2032
        }
2033
}
2034

    
2035
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
2036
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2037
                return;
2038
        if(buf == NULL || len <= 0)
2039
                return;
2040
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2041
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
2042
                return;
2043
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2044
        /* Get a string out of the data */
2045
        char *text = g_malloc0(len+1);
2046
        memcpy(text, buf, len);
2047
        *(text+len) = '\0';
2048
        JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
2049
        g_slist_foreach(participant->listeners, janus_videoroom_relay_data_packet, text);
2050
        g_free(text);
2051
}
2052

    
2053
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
2054
        /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */
2055
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2056
                return;
2057
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2058
        if(!session || session->destroyed || !session->participant)
2059
                return;
2060
        /* Check if it's an uplink (publisher) or downlink (viewer) issue */
2061
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2062
                if(!uplink) {
2063
                        janus_videoroom_participant *publisher = (janus_videoroom_participant *)session->participant;
2064
                        if(publisher) {
2065
                                /* Send an event on the handle to notify the application: it's
2066
                                 * up to the application to then choose a policy and enforce it */
2067
                                json_t *event = json_object();
2068
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2069
                                /* Also add info on what the current bitrate cap is */
2070
                                uint64_t bitrate = (publisher->bitrate ? publisher->bitrate : 256*1024);
2071
                                json_object_set_new(event, "current-bitrate", json_integer(bitrate));
2072
                                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2073
                                json_decref(event);
2074
                                event = NULL;
2075
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event_text, NULL, NULL);
2076
                                g_free(event_text);
2077
                                event_text = NULL;
2078
                        }
2079
                } else {
2080
                        JANUS_LOG(LOG_WARN, "Got a slow uplink on a VideoRoom publisher? Weird, because it doesn't receive media...\n");
2081
                }
2082
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2083
                if(uplink) {
2084
                        janus_videoroom_listener *viewer = (janus_videoroom_listener *)session->participant;
2085
                        if(viewer) {
2086
                                /* Send an event on the handle to notify the application: it's
2087
                                 * up to the application to then choose a policy and enforce it */
2088
                                json_t *event = json_object();
2089
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2090
                                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2091
                                json_decref(event);
2092
                                event = NULL;
2093
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event_text, NULL, NULL);
2094
                                g_free(event_text);
2095
                                event_text = NULL;
2096
                        }
2097
                } else {
2098
                        JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
2099
                }
2100
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2101
                /* TBD. */
2102
        }
2103
}
2104

    
2105
void janus_videoroom_hangup_media(janus_plugin_session *handle) {
2106
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
2107
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2108
                return;
2109
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2110
        if(!session) {
2111
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2112
                return;
2113
        }
2114
        session->started = FALSE;
2115
        if(session->destroyed)
2116
                return;
2117
        if(g_atomic_int_add(&session->hangingup, 1))
2118
                return;
2119
        /* Send an event to the browser and tell the PeerConnection is over */
2120
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2121
                /* This publisher just 'unpublished' */
2122
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2123
                if(participant->sdp)
2124
                        g_free(participant->sdp);
2125
                participant->sdp = NULL;
2126
                participant->firefox = FALSE;
2127
                participant->audio_active = FALSE;
2128
                participant->video_active = FALSE;
2129
                participant->remb_startup = 4;
2130
                participant->remb_latest = 0;
2131
                participant->fir_latest = 0;
2132
                participant->fir_seq = 0;
2133
                /* Get rid of the recorders, if available */
2134
                if(participant->arc) {
2135
                        janus_recorder_close(participant->arc);
2136
                        JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
2137
                        janus_recorder_free(participant->arc);
2138
                }
2139
                participant->arc = NULL;
2140
                if(participant->vrc) {
2141
                        janus_recorder_close(participant->vrc);
2142
                        JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
2143
                        janus_recorder_free(participant->vrc);
2144
                }
2145
                participant->vrc = NULL;
2146
                janus_mutex_lock(&participant->listeners_mutex);
2147
                while(participant->listeners) {
2148
                        janus_videoroom_listener *l = (janus_videoroom_listener *)participant->listeners->data;
2149
                        if(l) {
2150
                                participant->listeners = g_slist_remove(participant->listeners, l);
2151
                                l->feed = NULL;
2152
                        }
2153
                }
2154
                janus_mutex_unlock(&participant->listeners_mutex);
2155
                json_t *event = json_object();
2156
                json_object_set_new(event, "videoroom", json_string("event"));
2157
                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2158
                json_object_set_new(event, "unpublished", json_integer(participant->user_id));
2159
                char *unpub_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2160
                json_decref(event);
2161
                GHashTableIter iter;
2162
                gpointer value;
2163
                if(participant && participant->room) {
2164
                        if(!participant->room->destroyed) {
2165
                                janus_mutex_lock(&participant->room->participants_mutex);
2166
                                g_hash_table_iter_init(&iter, participant->room->participants);
2167
                                while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2168
                                        janus_videoroom_participant *p = value;
2169
                                        if(p && p->session && p != participant) {
2170
                                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2171
                                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, unpub_text, NULL, NULL);
2172
                                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
2173
                                        }
2174
                                }
2175
                                janus_mutex_unlock(&participant->room->participants_mutex);
2176
                        }
2177
                }
2178
                g_free(unpub_text);
2179
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2180
                /* Get rid of listener */
2181
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2182
                if(listener) {
2183
                        listener->paused = TRUE;
2184
                        janus_videoroom_participant *publisher = listener->feed;
2185
                        if(publisher != NULL) {
2186
                                janus_mutex_lock(&publisher->listeners_mutex);
2187
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
2188
                                janus_mutex_unlock(&publisher->listeners_mutex);
2189
                                listener->feed = NULL;
2190
                        }
2191
                }
2192
                /* TODO Should we close the handle as well? */
2193
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2194
                /* Do the same, but for all sub-listener */
2195
                janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
2196
                GSList *ps = listener->listeners;
2197
                while(ps) {
2198
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
2199
                        if(l) {
2200
                                l->paused = TRUE;
2201
                                janus_videoroom_participant *publisher = l->feed;
2202
                                if(publisher != NULL) {
2203
                                        janus_mutex_lock(&publisher->listeners_mutex);
2204
                                        publisher->listeners = g_slist_remove(publisher->listeners, l);
2205
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2206
                                        l->feed = NULL;
2207
                                }
2208
                        }
2209
                        /* TODO Should we close the handle as well? */
2210
                        ps = ps->next;
2211
                }
2212
                /* TODO Should we close the handle as well? */
2213
        }
2214
}
2215

    
2216
/* Thread to handle incoming messages */
2217
static void *janus_videoroom_handler(void *data) {
2218
        JANUS_LOG(LOG_VERB, "Joining VideoRoom handler thread\n");
2219
        janus_videoroom_message *msg = NULL;
2220
        int error_code = 0;
2221
        char error_cause[512];
2222
        json_t *root = NULL;
2223
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
2224
                msg = g_async_queue_pop(messages);
2225
                if(msg == NULL)
2226
                        continue;
2227
                if(msg == &exit_message)
2228
                        break;
2229
                if(msg->handle == NULL) {
2230
                        janus_videoroom_message_free(msg);
2231
                        continue;
2232
                }
2233
                janus_videoroom_session *session = NULL;
2234
                janus_mutex_lock(&sessions_mutex);
2235
                if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
2236
                        session = (janus_videoroom_session *)msg->handle->plugin_handle;
2237
                }
2238
                janus_mutex_unlock(&sessions_mutex);
2239
                if(!session) {
2240
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2241
                        janus_videoroom_message_free(msg);
2242
                        continue;
2243
                }
2244
                if(session->destroyed) {
2245
                        janus_videoroom_message_free(msg);
2246
                        continue;
2247
                }
2248
                /* Handle request */
2249
                error_code = 0;
2250
                root = NULL;
2251
                if(msg->message == NULL) {
2252
                        JANUS_LOG(LOG_ERR, "No message??\n");
2253
                        error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
2254
                        g_snprintf(error_cause, 512, "%s", "No message??");
2255
                        goto error;
2256
                }
2257
                root = msg->message;
2258
                /* Get the request first */
2259
                JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
2260
                        error_code, error_cause, TRUE,
2261
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2262
                if(error_code != 0)
2263
                        goto error;
2264
                json_t *request = json_object_get(root, "request");
2265
                const char *request_text = json_string_value(request);
2266
                json_t *event = NULL;
2267
                /* 'create' and 'destroy' are handled synchronously: what kind of participant is this session referring to? */
2268
                if(session->participant_type == janus_videoroom_p_type_none) {
2269
                        JANUS_LOG(LOG_VERB, "Configuring new participant\n");
2270
                        /* Not configured yet, we need to do this now */
2271
                        if(strcasecmp(request_text, "join") && strcasecmp(request_text, "joinandconfigure")) {
2272
                                JANUS_LOG(LOG_ERR, "Invalid request on unconfigured participant\n");
2273
                                error_code = JANUS_VIDEOROOM_ERROR_JOIN_FIRST;
2274
                                g_snprintf(error_cause, 512, "Invalid request on unconfigured participant");
2275
                                goto error;
2276
                        }
2277
                        JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
2278
                                error_code, error_cause, TRUE,
2279
                                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2280
                        if(error_code != 0)
2281
                                goto error;
2282
                        json_t *room = json_object_get(root, "room");
2283
                        guint64 room_id = json_integer_value(room);
2284
                        janus_mutex_lock(&rooms_mutex);
2285
                        janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
2286
                        if(videoroom == NULL) {
2287
                                janus_mutex_unlock(&rooms_mutex);
2288
                                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2289
                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2290
                                g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2291
                                goto error;
2292
                        }
2293
                        if(videoroom->destroyed) {
2294
                                janus_mutex_unlock(&rooms_mutex);
2295
                                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2296
                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2297
                                g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2298
                                goto error;
2299
                        }
2300
                        /* A pin may be required for this action */
2301
                        JANUS_CHECK_SECRET(videoroom->room_pin, root, "pin", error_code, error_cause,
2302
                                JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2303
                        if(error_code != 0) {
2304
                                janus_mutex_unlock(&rooms_mutex);
2305
                                goto error;
2306
                        }
2307
                        janus_mutex_unlock(&rooms_mutex);
2308

    
2309
                        json_t *ptype = json_object_get(root, "ptype");
2310
                        const char *ptype_text = json_string_value(ptype);
2311
                        if(!strcasecmp(ptype_text, "publisher")) {
2312
                                JANUS_LOG(LOG_VERB, "Configuring new publisher\n");
2313
                                JANUS_VALIDATE_JSON_OBJECT(root, publisher_parameters,
2314
                                        error_code, error_cause, TRUE,
2315
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2316
                                if(error_code != 0)
2317
                                        goto error;
2318
                                json_t *display = json_object_get(root, "display");
2319
                                const char *display_text = display ? json_string_value(display) : NULL;
2320
                                guint64 user_id = 0;
2321
                                json_t *id = json_object_get(root, "id");
2322
                                if(id) {
2323
                                        user_id = json_integer_value(id);
2324
                                        janus_mutex_lock(&videoroom->participants_mutex);
2325
                                        if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
2326
                                                janus_mutex_unlock(&videoroom->participants_mutex);
2327
                                                /* User ID already taken */
2328
                                                JANUS_LOG(LOG_ERR, "User ID %"SCNu64" already exists\n", user_id);
2329
                                                error_code = JANUS_VIDEOROOM_ERROR_ID_EXISTS;
2330
                                                g_snprintf(error_cause, 512, "User ID %"SCNu64" already exists", user_id);
2331
                                                goto error;
2332
                                        }
2333
                                        janus_mutex_unlock(&videoroom->participants_mutex);
2334
                                }
2335
                                if(user_id == 0) {
2336
                                        /* Generate a random ID */
2337
                                        janus_mutex_lock(&videoroom->participants_mutex);
2338
                                        while(user_id == 0) {
2339
                                                user_id = g_random_int();
2340
                                                if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
2341
                                                        /* User ID already taken, try another one */
2342
                                                        user_id = 0;
2343
                                                }
2344
                                        }
2345
                                        janus_mutex_unlock(&videoroom->participants_mutex);
2346
                                }
2347
                                JANUS_LOG(LOG_VERB, "  -- Publisher ID: %"SCNu64"\n", user_id);
2348
                                json_t *audio = NULL, *video = NULL, *bitrate = NULL, *record = NULL, *recfile = NULL;
2349
                                if(!strcasecmp(request_text, "joinandconfigure")) {
2350
                                        /* Also configure (or publish a new feed) audio/video/bitrate for this new publisher */
2351
                                        /* join_parameters were validated earlier. */
2352
                                        audio = json_object_get(root, "audio");
2353
                                        video = json_object_get(root, "video");
2354
                                        bitrate = json_object_get(root, "bitrate");
2355
                                        record = json_object_get(root, "record");
2356
                                        recfile = json_object_get(root, "filename");
2357
                                }
2358
                                janus_videoroom_participant *publisher = g_malloc0(sizeof(janus_videoroom_participant));
2359
                                if(publisher == NULL) {
2360
                                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
2361
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2362
                                        g_snprintf(error_cause, 512, "Memory error");
2363
                                        goto error;
2364
                                }
2365
                                publisher->session = session;
2366
                                publisher->room = videoroom;
2367
                                publisher->user_id = user_id;
2368
                                publisher->display = display_text ? g_strdup(display_text) : NULL;
2369
                                publisher->sdp = NULL;                /* We'll deal with this later */
2370
                                publisher->audio = FALSE;        /* We'll deal with this later */
2371
                                publisher->video = FALSE;        /* We'll deal with this later */
2372
                                publisher->data = FALSE;        /* We'll deal with this later */
2373
                                publisher->audio_active = FALSE;
2374
                                publisher->video_active = FALSE;
2375
                                publisher->recording_active = FALSE;
2376
                                publisher->recording_base = NULL;
2377
                                publisher->arc = NULL;
2378
                                publisher->vrc = NULL;
2379
                                publisher->firefox = FALSE;
2380
                                publisher->bitrate = videoroom->bitrate;
2381
                                publisher->listeners = NULL;
2382
                                janus_mutex_init(&publisher->listeners_mutex);
2383
                                publisher->audio_pt = OPUS_PT;
2384
                                switch(videoroom->acodec) {
2385
                                        case JANUS_VIDEOROOM_OPUS:
2386
                                                publisher->audio_pt = OPUS_PT;
2387
                                                break;
2388
                                        case JANUS_VIDEOROOM_ISAC_32K:
2389
                                                publisher->audio_pt = ISAC32_PT;
2390
                                                break;
2391
                                        case JANUS_VIDEOROOM_ISAC_16K:
2392
                                                publisher->audio_pt = ISAC16_PT;
2393
                                                break;
2394
                                        case JANUS_VIDEOROOM_PCMU:
2395
                                                publisher->audio_pt = PCMU_PT;
2396
                                                break;
2397
                                        case JANUS_VIDEOROOM_PCMA:
2398
                                                publisher->audio_pt = PCMA_PT;
2399
                                                break;
2400
                                        default:
2401
                                                /* Shouldn't happen */
2402
                                                publisher->audio_pt = OPUS_PT;
2403
                                                break;
2404
                                }
2405
                                switch(videoroom->vcodec) {
2406
                                        case JANUS_VIDEOROOM_VP8:
2407
                                                publisher->video_pt = VP8_PT;
2408
                                                break;
2409
                                        case JANUS_VIDEOROOM_VP9:
2410
                                                publisher->video_pt = VP9_PT;
2411
                                                break;
2412
                                        case JANUS_VIDEOROOM_H264:
2413
                                                publisher->video_pt = H264_PT;
2414
                                                break;
2415
                                        default:
2416
                                                /* Shouldn't happen */
2417
                                                publisher->video_pt = VP8_PT;
2418
                                                break;
2419
                                }
2420
                                publisher->audio_ssrc = g_random_int();
2421
                                publisher->video_ssrc = g_random_int();
2422
                                publisher->remb_startup = 4;
2423
                                publisher->remb_latest = 0;
2424
                                publisher->fir_latest = 0;
2425
                                publisher->fir_seq = 0;
2426
                                janus_mutex_init(&publisher->rtp_forwarders_mutex);
2427
                                publisher->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_rtp_forwarder_free_helper);
2428
                                publisher->udp_sock = -1;
2429
                                /* In case we also wanted to configure */
2430
                                if(audio) {
2431
                                        publisher->audio_active = json_is_true(audio);
2432
                                        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);
2433
                                }
2434
                                if(video) {
2435
                                        publisher->video_active = json_is_true(video);
2436
                                        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);
2437
                                }
2438
                                if(bitrate) {
2439
                                        publisher->bitrate = json_integer_value(bitrate);
2440
                                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", publisher->bitrate, publisher->room->room_id, publisher->user_id);
2441
                                }
2442
                                if(record) {
2443
                                        publisher->recording_active = json_is_true(record);
2444
                                        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);
2445
                                }
2446
                                if(recfile) {
2447
                                        publisher->recording_base = g_strdup(json_string_value(recfile));
2448
                                        JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", publisher->recording_base, publisher->room->room_id, publisher->user_id);
2449
                                }
2450
                                /* Done */
2451
                                session->participant_type = janus_videoroom_p_type_publisher;
2452
                                session->participant = publisher;
2453
                                /* Return a list of all available publishers (those with an SDP available, that is) */
2454
                                json_t *list = json_array();
2455
                                GHashTableIter iter;
2456
                                gpointer value;
2457
                                janus_mutex_lock(&videoroom->participants_mutex);
2458
                                g_hash_table_insert(videoroom->participants, GUINT_TO_POINTER(user_id), publisher);
2459
                                g_hash_table_iter_init(&iter, videoroom->participants);
2460
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2461
                                        janus_videoroom_participant *p = value;
2462
                                        if(p == publisher || !p->sdp) {
2463
                                                continue;
2464
                                        }
2465
                                        json_t *pl = json_object();
2466
                                        json_object_set_new(pl, "id", json_integer(p->user_id));
2467
                                        if(p->display)
2468
                                                json_object_set_new(pl, "display", json_string(p->display));
2469
                                        json_array_append_new(list, pl);
2470
                                }
2471
                                janus_mutex_unlock(&videoroom->participants_mutex);
2472
                                event = json_object();
2473
                                json_object_set_new(event, "videoroom", json_string("joined"));
2474
                                json_object_set_new(event, "room", json_integer(videoroom->room_id));
2475
                                json_object_set_new(event, "description", json_string(videoroom->room_name));
2476
                                json_object_set_new(event, "id", json_integer(user_id));
2477
                                json_object_set_new(event, "publishers", list);
2478
                        } else if(!strcasecmp(ptype_text, "listener")) {
2479
                                JANUS_LOG(LOG_VERB, "Configuring new listener\n");
2480
                                /* This is a new listener */
2481
                                JANUS_VALIDATE_JSON_OBJECT(root, listener_parameters,
2482
                                        error_code, error_cause, TRUE,
2483
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2484
                                if(error_code != 0)
2485
                                        goto error;
2486
                                json_t *feed = json_object_get(root, "feed");
2487
                                guint64 feed_id = json_integer_value(feed);
2488
                                json_t *audio = json_object_get(root, "audio");
2489
                                json_t *video = json_object_get(root, "video");
2490
                                json_t *data = json_object_get(root, "data");
2491
                                janus_mutex_lock(&videoroom->participants_mutex);
2492
                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
2493
                                janus_mutex_unlock(&videoroom->participants_mutex);
2494
                                if(publisher == NULL || publisher->sdp == NULL) {
2495
                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
2496
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2497
                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
2498
                                        goto error;
2499
                                } else {
2500
                                        janus_videoroom_listener *listener = g_malloc0(sizeof(janus_videoroom_listener));
2501
                                        if(listener == NULL) {
2502
                                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
2503
                                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2504
                                                g_snprintf(error_cause, 512, "Memory error");
2505
                                                goto error;
2506
                                        }
2507
                                        listener->session = session;
2508
                                        listener->room = videoroom;
2509
                                        listener->feed = publisher;
2510
                                        /* Initialize the listener context */
2511
                                        listener->context.a_last_ssrc = 0;
2512
                                        listener->context.a_last_ssrc = 0;
2513
                                        listener->context.a_last_ts = 0;
2514
                                        listener->context.a_base_ts = 0;
2515
                                        listener->context.a_base_ts_prev = 0;
2516
                                        listener->context.v_last_ssrc = 0;
2517
                                        listener->context.v_last_ts = 0;
2518
                                        listener->context.v_base_ts = 0;
2519
                                        listener->context.v_base_ts_prev = 0;
2520
                                        listener->context.a_last_seq = 0;
2521
                                        listener->context.a_base_seq = 0;
2522
                                        listener->context.a_base_seq_prev = 0;
2523
                                        listener->context.v_last_seq = 0;
2524
                                        listener->context.v_base_seq = 0;
2525
                                        listener->context.v_base_seq_prev = 0;
2526
                                        listener->audio = audio ? json_is_true(audio) : TRUE;        /* True by default */
2527
                                        if(!publisher->audio)
2528
                                                listener->audio = FALSE;        /* ... unless the publisher isn't sending any audio */
2529
                                        listener->video = video ? json_is_true(video) : TRUE;        /* True by default */
2530
                                        if(!publisher->video)
2531
                                                listener->video = FALSE;        /* ... unless the publisher isn't sending any video */
2532
                                        listener->data = data ? json_is_true(data) : TRUE;        /* True by default */
2533
                                        if(!publisher->data)
2534
                                                listener->data = FALSE;        /* ... unless the publisher isn't sending any data */
2535
                                        listener->paused = TRUE;        /* We need an explicit start from the listener */
2536
                                        listener->parent = NULL;
2537
                                        session->participant = listener;
2538
                                        janus_mutex_lock(&publisher->listeners_mutex);
2539
                                        publisher->listeners = g_slist_append(publisher->listeners, listener);
2540
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2541
                                        event = json_object();
2542
                                        json_object_set_new(event, "videoroom", json_string("attached"));
2543
                                        json_object_set_new(event, "room", json_integer(videoroom->room_id));
2544
                                        json_object_set_new(event, "id", json_integer(feed_id));
2545
                                        if(publisher->display)
2546
                                                json_object_set_new(event, "display", json_string(publisher->display));
2547
                                        session->participant_type = janus_videoroom_p_type_subscriber;
2548
                                        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
2549
                                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2550
                                        json_decref(event);
2551
                                        /* Negotiate by sending the selected publisher SDP back */
2552
                                        if(publisher->sdp != NULL) {
2553
                                                /* How long will the gateway take to push the event? */
2554
                                                g_atomic_int_set(&session->hangingup, 0);
2555
                                                gint64 start = janus_get_monotonic_time();
2556
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, "offer", publisher->sdp);
2557
                                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
2558
                                                JANUS_LOG(LOG_VERB, "  >> %d\n", res);
2559
                                                g_free(event_text);
2560
                                                root = NULL;
2561
                                                janus_videoroom_message_free(msg);
2562
                                                continue;
2563
                                        }
2564
                                        g_free(event_text);
2565
                                }
2566
                        } else if(!strcasecmp(ptype_text, "muxed-listener")) {
2567
                                /* This is a new Multiplexed listener */
2568
                                JANUS_LOG(LOG_INFO, "Configuring new Multiplexed listener\n");
2569
                                /* Any feed we want to attach to already? */
2570
                                GList *list = NULL;
2571
                                JANUS_VALIDATE_JSON_OBJECT(root, feeds_parameters,
2572
                                        error_code, error_cause, TRUE,
2573
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2574
                                if(error_code != 0)
2575
                                        goto error;
2576
                                json_t *feeds = json_object_get(root, "feeds");
2577
                                if(feeds && json_array_size(feeds) > 0) {
2578
                                        unsigned int i = 0;
2579
                                        int problem = 0;
2580
                                        for(i=0; i<json_array_size(feeds); i++) {
2581
                                                if(videoroom->destroyed) {
2582
                                                        problem = 1;
2583
                                                        JANUS_LOG(LOG_ERR, "Room destroyed");
2584
                                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2585
                                                        g_snprintf(error_cause, 512, "Room destroyed");
2586
                                                        break;
2587
                                                }
2588
                                                json_t *feed = json_array_get(feeds, i);
2589
                                                if(!feed || !json_is_integer(feed)) {
2590
                                                        problem = 1;
2591
                                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
2592
                                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2593
                                                        g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
2594
                                                        break;
2595
                                                }
2596
                                                uint64_t feed_id = json_integer_value(feed);
2597
                                                janus_mutex_lock(&videoroom->participants_mutex);
2598
                                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
2599
                                                janus_mutex_unlock(&videoroom->participants_mutex);
2600
                                                if(publisher == NULL) { //~ || publisher->sdp == NULL) {
2601
                                                        /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
2602
                                                        problem = 1;
2603
                                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
2604
                                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2605
                                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
2606
                                                        break;
2607
                                                }
2608
                                                list = g_list_prepend(list, GUINT_TO_POINTER(feed_id));
2609
                                                JANUS_LOG(LOG_INFO, "  -- Subscribing to feed %"SCNu64"\n", feed_id);
2610
                                        }
2611
                                        if(problem) {
2612
                                                goto error;
2613
                                        }
2614
                                }
2615
                                /* Allocate listener */
2616
                                janus_videoroom_listener_muxed *listener = g_malloc0(sizeof(janus_videoroom_listener_muxed));
2617
                                if(listener == NULL) {
2618
                                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
2619
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2620
                                        g_snprintf(error_cause, 512, "Memory error");
2621
                                        goto error;
2622
                                }
2623
                                listener->session = session;
2624
                                listener->room = videoroom;
2625
                                session->participant_type = janus_videoroom_p_type_subscriber_muxed;
2626
                                session->participant = listener;
2627
                                /* Ack that we created the listener */
2628
                                event = json_object();
2629
                                json_object_set_new(event, "videoroom", json_string("muxed-created"));
2630
                                json_object_set_new(event, "room", json_integer(videoroom->room_id));
2631
                                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
2632
                                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2633
                                json_decref(event);
2634
                                /* How long will the gateway take to push the event? */
2635
                                gint64 start = janus_get_monotonic_time();
2636
                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
2637
                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
2638
                                JANUS_LOG(LOG_VERB, "  >> %d\n", res);
2639
                                g_free(event_text);
2640
                                root = NULL;
2641
                                /* Attach to feeds if needed */
2642
                                if(list != NULL) {
2643
                                        JANUS_LOG(LOG_INFO, "Subscribing to %d feeds\n", g_list_length(list));
2644
                                        list = g_list_reverse(list);
2645
                                        if(videoroom->destroyed || janus_videoroom_muxed_subscribe(listener, list, msg->transaction) < 0) {
2646
                                                JANUS_LOG(LOG_ERR, "Error subscribing!\n");
2647
                                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
2648
                                                g_snprintf(error_cause, 512, "Error subscribing!");
2649
                                                goto error;
2650
                                        }
2651
                                }
2652
                                janus_videoroom_message_free(msg);
2653
                                continue;
2654
                        } else {
2655
                                JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
2656
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2657
                                g_snprintf(error_cause, 512, "Invalid element (ptype)");
2658
                                goto error;
2659
                        }
2660
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
2661
                        /* Handle this publisher */
2662
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2663
                        if(participant == NULL) {
2664
                                JANUS_LOG(LOG_ERR, "Invalid participant instance\n");
2665
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2666
                                g_snprintf(error_cause, 512, "Invalid participant instance");
2667
                                goto error;
2668
                        }
2669
                        if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")) {
2670
                                JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
2671
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
2672
                                g_snprintf(error_cause, 512, "Already in as a publisher on this handle");
2673
                                goto error;
2674
                        } else if(!strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish")) {
2675
                                if(!strcasecmp(request_text, "publish") && participant->sdp) {
2676
                                        JANUS_LOG(LOG_ERR, "Can't publish, already published\n");
2677
                                        error_code = JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED;
2678
                                        g_snprintf(error_cause, 512, "Can't publish, already published");
2679
                                        goto error;
2680
                                }
2681
                                /* Configure (or publish a new feed) audio/video/bitrate for this publisher */
2682
                                JANUS_VALIDATE_JSON_OBJECT(root, publish_parameters,
2683
                                        error_code, error_cause, TRUE,
2684
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2685
                                if(error_code != 0)
2686
                                        goto error;
2687
                                json_t *audio = json_object_get(root, "audio");
2688
                                json_t *video = json_object_get(root, "video");
2689
                                json_t *bitrate = json_object_get(root, "bitrate");
2690
                                json_t *record = json_object_get(root, "record");
2691
                                json_t *recfile = json_object_get(root, "filename");
2692
                                if(audio) {
2693
                                        participant->audio_active = json_is_true(audio);
2694
                                        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);
2695
                                }
2696
                                if(video) {
2697
                                        participant->video_active = json_is_true(video);
2698
                                        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);
2699
                                }
2700
                                if(bitrate) {
2701
                                        participant->bitrate = json_integer_value(bitrate);
2702
                                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
2703
                                        /* Send a new REMB */
2704
                                        participant->remb_latest = janus_get_monotonic_time();
2705
                                        char rtcpbuf[24];
2706
                                        janus_rtcp_remb((char *)(&rtcpbuf), 24, participant->bitrate ? participant->bitrate : 256*1024);
2707
                                        gateway->relay_rtcp(msg->handle, 1, rtcpbuf, 24);
2708
                                }
2709
                                gboolean prev_recording_active = participant->recording_active;
2710
                                if(record) {
2711
                                        participant->recording_active = json_is_true(record);
2712
                                        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);
2713
                                }
2714
                                if(recfile) {
2715
                                        participant->recording_base = g_strdup(json_string_value(recfile));
2716
                                        JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", participant->recording_base, participant->room->room_id, participant->user_id);
2717
                                }
2718
                                /* Do we need to do something with the recordings right now? */
2719
                                if(participant->recording_active != prev_recording_active) {
2720
                                        /* Something changed */
2721
                                        if(!participant->recording_active) {
2722
                                                /* Not recording (anymore?) */
2723
                                                if(participant->arc) {
2724
                                                        janus_recorder_close(participant->arc);
2725
                                                        JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
2726
                                                        janus_recorder_free(participant->arc);
2727
                                                }
2728
                                                participant->arc = NULL;
2729
                                                if(participant->vrc) {
2730
                                                        janus_recorder_close(participant->vrc);
2731
                                                        JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
2732
                                                        janus_recorder_free(participant->vrc);
2733
                                                }
2734
                                                participant->vrc = NULL;
2735
                                        } else if(participant->recording_active && participant->sdp) {
2736
                                                /* We've started recording, send a PLI/FIR and go on */
2737
                                                char filename[255];
2738
                                                gint64 now = janus_get_real_time();
2739
                                                if(strstr(participant->sdp, "m=audio")) {
2740
                                                        memset(filename, 0, 255);
2741
                                                        if(participant->recording_base) {
2742
                                                                /* Use the filename and path we have been provided */
2743
                                                                g_snprintf(filename, 255, "%s-audio", participant->recording_base);
2744
                                                                participant->arc = janus_recorder_create(participant->room->rec_dir, 0, filename);
2745
                                                                if(participant->arc == NULL) {
2746
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2747
                                                                }
2748
                                                        } else {
2749
                                                                /* Build a filename */
2750
                                                                g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
2751
                                                                        participant->room->room_id, participant->user_id, now);
2752
                                                                participant->arc = janus_recorder_create(participant->room->rec_dir, 0, filename);
2753
                                                                if(participant->arc == NULL) {
2754
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2755
                                                                }
2756
                                                        }
2757
                                                }
2758
                                                if(strstr(participant->sdp, "m=video")) {
2759
                                                        memset(filename, 0, 255);
2760
                                                        if(participant->recording_base) {
2761
                                                                /* Use the filename and path we have been provided */
2762
                                                                g_snprintf(filename, 255, "%s-video", participant->recording_base);
2763
                                                                participant->vrc = janus_recorder_create(participant->room->rec_dir, 1, filename);
2764
                                                                if(participant->vrc == NULL) {
2765
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2766
                                                                }
2767
                                                        } else {
2768
                                                                /* Build a filename */
2769
                                                                g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
2770
                                                                        participant->room->room_id, participant->user_id, now);
2771
                                                                participant->vrc = janus_recorder_create(participant->room->rec_dir, 1, filename);
2772
                                                                if(participant->vrc == NULL) {
2773
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2774
                                                                }
2775
                                                        }
2776
                                                        /* Send a FIR */
2777
                                                        char buf[20];
2778
                                                        memset(buf, 0, 20);
2779
                                                        janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
2780
                                                        JANUS_LOG(LOG_VERB, "Recording video, sending FIR to %"SCNu64" (%s)\n",
2781
                                                                participant->user_id, participant->display ? participant->display : "??");
2782
                                                        gateway->relay_rtcp(participant->session->handle, 1, buf, 20);
2783
                                                        /* Send a PLI too, just in case... */
2784
                                                        memset(buf, 0, 12);
2785
                                                        janus_rtcp_pli((char *)&buf, 12);
2786
                                                        JANUS_LOG(LOG_VERB, "Recording video, sending PLI to %"SCNu64" (%s)\n",
2787
                                                                participant->user_id, participant->display ? participant->display : "??");
2788
                                                        gateway->relay_rtcp(participant->session->handle, 1, buf, 12);
2789
                                                }
2790
                                        }
2791
                                }
2792
                                /* Done */
2793
                                event = json_object();
2794
                                json_object_set_new(event, "videoroom", json_string("event"));
2795
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2796
                                json_object_set_new(event, "configured", json_string("ok"));
2797
                        } else if(!strcasecmp(request_text, "unpublish")) {
2798
                                /* This participant wants to unpublish */
2799
                                if(!participant->sdp) {
2800
                                        JANUS_LOG(LOG_ERR, "Can't unpublish, not published\n");
2801
                                        error_code = JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED;
2802
                                        g_snprintf(error_cause, 512, "Can't unpublish, not published");
2803
                                        goto error;
2804
                                }
2805
                                /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
2806
                                gateway->close_pc(session->handle);
2807
                                /* Done */
2808
                                event = json_object();
2809
                                json_object_set_new(event, "videoroom", json_string("event"));
2810
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2811
                                json_object_set_new(event, "unpublished", json_string("ok"));
2812
                        } else if(!strcasecmp(request_text, "leave")) {
2813
                                /* This publisher is leaving, tell everybody */
2814
                                event = json_object();
2815
                                json_object_set_new(event, "videoroom", json_string("event"));
2816
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2817
                                json_object_set_new(event, "leaving", json_integer(participant->user_id));
2818
                                char *leaving_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2819
                                GHashTableIter iter;
2820
                                gpointer value;
2821
                                if(participant->room) {
2822
                                        if(!participant->room->destroyed) {
2823
                                                janus_mutex_lock(&participant->room->participants_mutex);
2824
                                                g_hash_table_iter_init(&iter, participant->room->participants);
2825
                                                while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2826
                                                        janus_videoroom_participant *p = value;
2827
                                                        if(p == participant) {
2828
                                                                continue;        /* Skip the new publisher itself */
2829
                                                        }
2830
                                                        JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2831
                                                        int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
2832
                                                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
2833
                                                }
2834
                                                janus_mutex_unlock(&participant->room->participants_mutex);
2835
                                        }
2836
                                }
2837
                                g_free(leaving_text);
2838
                                /* Done */
2839
                                participant->audio_active = FALSE;
2840
                                participant->video_active = FALSE;
2841
                                session->started = FALSE;
2842
                                //~ session->destroy = TRUE;
2843
                        } else {
2844
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
2845
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
2846
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
2847
                                goto error;
2848
                        }
2849
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2850
                        /* Handle this listener */
2851
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2852
                        if(listener == NULL) {
2853
                                JANUS_LOG(LOG_ERR, "Invalid listener instance\n");
2854
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2855
                                g_snprintf(error_cause, 512, "Invalid listener instance");
2856
                                goto error;
2857
                        }
2858
                        if(!strcasecmp(request_text, "join")) {
2859
                                JANUS_LOG(LOG_ERR, "Already in as a listener on this handle\n");
2860
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
2861
                                g_snprintf(error_cause, 512, "Already in as a listener on this handle");
2862
                                goto error;
2863
                        } else if(!strcasecmp(request_text, "start")) {
2864
                                /* Start/restart receiving the publisher streams */
2865
                                janus_videoroom_participant *publisher = listener->feed;
2866
                                listener->paused = FALSE;
2867
                                event = json_object();
2868
                                json_object_set_new(event, "videoroom", json_string("event"));
2869
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2870
                                json_object_set_new(event, "started", json_string("ok"));
2871
                                if(publisher) {
2872
                                        /* Send a FIR */
2873
                                        char buf[20];
2874
                                        memset(buf, 0, 20);
2875
                                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
2876
                                        JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2877
                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
2878
                                        /* Send a PLI too, just in case... */
2879
                                        memset(buf, 0, 12);
2880
                                        janus_rtcp_pli((char *)&buf, 12);
2881
                                        JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2882
                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
2883
                                }
2884
                        } else if(!strcasecmp(request_text, "configure")) {
2885
                                JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
2886
                                        error_code, error_cause, TRUE,
2887
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2888
                                if(error_code != 0)
2889
                                        goto error;
2890
                                json_t *audio = json_object_get(root, "audio");
2891
                                json_t *video = json_object_get(root, "video");
2892
                                json_t *data = json_object_get(root, "data");
2893
                                /* Update the audio/video/data flags, if set */
2894
                                janus_videoroom_participant *publisher = listener->feed;
2895
                                if(publisher) {
2896
                                        if(audio && publisher->audio)
2897
                                                listener->audio = json_is_true(audio);
2898
                                        if(video && publisher->video)
2899
                                                listener->video = json_is_true(video);
2900
                                        if(data && publisher->data)
2901
                                                listener->data = json_is_true(data);
2902
                                }
2903
                                event = json_object();
2904
                                json_object_set_new(event, "videoroom", json_string("event"));
2905
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2906
                                json_object_set_new(event, "configured", json_string("ok"));
2907
                        } else if(!strcasecmp(request_text, "pause")) {
2908
                                /* Stop receiving the publisher streams for a while */
2909
                                listener->paused = TRUE;
2910
                                event = json_object();
2911
                                json_object_set_new(event, "videoroom", json_string("event"));
2912
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2913
                                json_object_set_new(event, "paused", json_string("ok"));
2914
                        } else if(!strcasecmp(request_text, "switch")) {
2915
                                /* This listener wants to switch to a different publisher */
2916
                                JANUS_VALIDATE_JSON_OBJECT(root, listener_parameters,
2917
                                        error_code, error_cause, TRUE,
2918
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2919
                                if(error_code != 0)
2920
                                        goto error;
2921
                                json_t *feed = json_object_get(root, "feed");
2922
                                guint64 feed_id = json_integer_value(feed);
2923
                                json_t *audio = json_object_get(root, "audio");
2924
                                json_t *video = json_object_get(root, "video");
2925
                                json_t *data = json_object_get(root, "data");
2926
                                if(!listener->room) {
2927
                                        JANUS_LOG(LOG_ERR, "Room Destroyed \n");
2928
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2929
                                        g_snprintf(error_cause, 512, "No such room ");
2930
                                        goto error;
2931
                                }
2932
                                if(listener->room->destroyed) {
2933
                                        JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")\n", listener->room->room_id);
2934
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2935
                                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", listener->room->room_id);
2936
                                        goto error;
2937
                                }
2938
                                janus_mutex_lock(&listener->room->participants_mutex);
2939
                                janus_videoroom_participant *publisher = g_hash_table_lookup(listener->room->participants, GUINT_TO_POINTER(feed_id));
2940
                                janus_mutex_unlock(&listener->room->participants_mutex);
2941
                                if(publisher == NULL || publisher->sdp == NULL) {
2942
                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
2943
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2944
                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
2945
                                        goto error;
2946
                                }
2947
                                gboolean paused = listener->paused;
2948
                                listener->paused = TRUE;
2949
                                /* Unsubscribe from the previous publisher */
2950
                                janus_videoroom_participant *prev_feed = listener->feed;
2951
                                if(prev_feed) {
2952
                                        janus_mutex_lock(&prev_feed->listeners_mutex);
2953
                                        prev_feed->listeners = g_slist_remove(prev_feed->listeners, listener);
2954
                                        janus_mutex_unlock(&prev_feed->listeners_mutex);
2955
                                        listener->feed = NULL;
2956
                                }
2957
                                /* Subscribe to the new one */
2958
                                listener->audio = audio ? json_is_true(audio) : TRUE;        /* True by default */
2959
                                if(!publisher->audio)
2960
                                        listener->audio = FALSE;        /* ... unless the publisher isn't sending any audio */
2961
                                listener->video = video ? json_is_true(video) : TRUE;        /* True by default */
2962
                                if(!publisher->video)
2963
                                        listener->video = FALSE;        /* ... unless the publisher isn't sending any video */
2964
                                listener->data = data ? json_is_true(data) : TRUE;        /* True by default */
2965
                                if(!publisher->data)
2966
                                        listener->data = FALSE;        /* ... unless the publisher isn't sending any data */
2967
                                janus_mutex_lock(&publisher->listeners_mutex);
2968
                                publisher->listeners = g_slist_append(publisher->listeners, listener);
2969
                                janus_mutex_unlock(&publisher->listeners_mutex);
2970
                                listener->feed = publisher;
2971
                                /* Send a FIR to the new publisher */
2972
                                char buf[20];
2973
                                memset(buf, 0, 20);
2974
                                janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
2975
                                JANUS_LOG(LOG_VERB, "Switching existing listener to new publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2976
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
2977
                                /* Send a PLI too, just in case... */
2978
                                memset(buf, 0, 12);
2979
                                janus_rtcp_pli((char *)&buf, 12);
2980
                                JANUS_LOG(LOG_VERB, "Switching existing listener to new publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2981
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
2982
                                /* Done */
2983
                                listener->paused = paused;
2984
                                event = json_object();
2985
                                json_object_set_new(event, "videoroom", json_string("event"));
2986
                                json_object_set_new(event, "switched", json_string("ok"));
2987
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2988
                                json_object_set_new(event, "id", json_integer(feed_id));
2989
                                if(publisher->display)
2990
                                        json_object_set_new(event, "display", json_string(publisher->display));
2991
                        } else if(!strcasecmp(request_text, "leave")) {
2992
                                janus_videoroom_participant *publisher = listener->feed;
2993
                                if(publisher != NULL) {
2994
                                        janus_mutex_lock(&publisher->listeners_mutex);
2995
                                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
2996
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2997
                                        listener->feed = NULL;
2998
                                }
2999
                                event = json_object();
3000
                                json_object_set_new(event, "videoroom", json_string("event"));
3001
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3002
                                json_object_set_new(event, "left", json_string("ok"));
3003
                                session->started = FALSE;
3004
                        } else {
3005
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
3006
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
3007
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
3008
                                goto error;
3009
                        }
3010
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
3011
                        /* Handle this Multiplexed listener */
3012
                        janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
3013
                        if(listener == NULL) {
3014
                                JANUS_LOG(LOG_ERR, "Invalid Multiplexed listener instance\n");
3015
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3016
                                g_snprintf(error_cause, 512, "Invalid Multiplexed listener instance");
3017
                                goto error;
3018
                        }
3019
                        if(!strcasecmp(request_text, "join")) {
3020
                                JANUS_LOG(LOG_ERR, "Already in as a Multiplexed listener on this handle\n");
3021
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
3022
                                g_snprintf(error_cause, 512, "Already in as a Multiplexed listener on this handle");
3023
                                goto error;
3024
                        } else if(!strcasecmp(request_text, "add")) {
3025
                                /* Add new streams to subscribe to */
3026
                                GList *list = NULL;
3027
                                JANUS_VALIDATE_JSON_OBJECT(root, feeds_parameters,
3028
                                        error_code, error_cause, TRUE,
3029
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3030
                                if(error_code != 0)
3031
                                        goto error;
3032
                                json_t *feeds = json_object_get(root, "feeds");
3033
                                unsigned int i = 0;
3034
                                int problem = 0;
3035
                                if(!listener->room) {
3036
                                        JANUS_LOG(LOG_ERR, "Room Destroyed ");
3037
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3038
                                        g_snprintf(error_cause, 512, "No such room ");
3039
                                        goto error;
3040
                                }
3041
                                if(listener->room->destroyed) {
3042
                                        JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")", listener->room->room_id);
3043
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3044
                                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", listener->room->room_id);
3045
                                        goto error;
3046
                                }
3047
                                for(i=0; i<json_array_size(feeds); i++) {
3048
                                        json_t *feed = json_array_get(feeds, i);
3049
                                        if(listener->room->destroyed) {
3050
                                                problem = 1;
3051
                                                JANUS_LOG(LOG_ERR, "Room destroyed");
3052
                                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3053
                                                g_snprintf(error_cause, 512, "Room destroyed");
3054
                                                break;
3055
                                        }
3056
                                        if(!feed || !json_is_integer(feed)) {
3057
                                                problem = 1;
3058
                                                JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
3059
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3060
                                                g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
3061
                                                break;
3062
                                        }
3063
                                        uint64_t feed_id = json_integer_value(feed);
3064
                                        janus_mutex_lock(&listener->room->participants_mutex);
3065
                                        janus_videoroom_participant *publisher = g_hash_table_lookup(listener->room->participants, GUINT_TO_POINTER(feed_id));
3066
                                        janus_mutex_unlock(&listener->room->participants_mutex);
3067
                                        if(publisher == NULL) { //~ || publisher->sdp == NULL) {
3068
                                                /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
3069
                                                problem = 1;
3070
                                                JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
3071
                                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3072
                                                g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
3073
                                                break;
3074
                                        }
3075
                                        list = g_list_prepend(list, GUINT_TO_POINTER(feed_id));
3076
                                }
3077
                                if(problem) {
3078
                                        goto error;
3079
                                }
3080
                                list = g_list_reverse(list);
3081
                                if(janus_videoroom_muxed_subscribe(listener, list, msg->transaction) < 0) {
3082
                                        JANUS_LOG(LOG_ERR, "Error subscribing!\n");
3083
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
3084
                                        g_snprintf(error_cause, 512, "Error subscribing!");
3085
                                        goto error;
3086
                                }
3087
                                continue;
3088
                        } else if(!strcasecmp(request_text, "remove")) {
3089
                                /* Remove subscribed streams */
3090
                                GList *list = NULL;
3091
                                JANUS_VALIDATE_JSON_OBJECT(root, feeds_parameters,
3092
                                        error_code, error_cause, TRUE,
3093
                                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3094
                                if(error_code != 0)
3095
                                        goto error;
3096
                                json_t *feeds = json_object_get(root, "feeds");
3097
                                unsigned int i = 0;
3098
                                int error = 0;
3099
                                for(i=0; i<json_array_size(feeds); i++) {
3100
                                        json_t *feed = json_array_get(feeds, i);
3101
                                        if(!feed || !json_is_integer(feed)) {
3102
                                                error = 1;
3103
                                                break;
3104
                                        }
3105
                                        list = g_list_prepend(list, GUINT_TO_POINTER(json_integer_value(feed)));
3106
                                }
3107
                                if(error) {
3108
                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
3109
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3110
                                        g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
3111
                                        goto error;
3112
                                }
3113
                                list = g_list_reverse(list);
3114
                                
3115
                                if(!listener->room) {
3116
                                        JANUS_LOG(LOG_ERR, "Error unsubscribing!\n");
3117
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
3118
                                        g_snprintf(error_cause, 512, "Error unsubscribing!");
3119
                                        goto error;
3120
                                }
3121
                                if(janus_videoroom_muxed_unsubscribe(listener, list, msg->transaction) < 0) {
3122
                                        JANUS_LOG(LOG_ERR, "Error unsubscribing!\n");
3123
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
3124
                                        g_snprintf(error_cause, 512, "Error unsubscribing!");
3125
                                        goto error;
3126
                                }
3127
                                continue;
3128
                        } else if(!strcasecmp(request_text, "start")) {
3129
                                /* Start/restart receiving the publishers streams */
3130
                                /* TODO */
3131
                                event = json_object();
3132
                                json_object_set_new(event, "videoroom", json_string("event"));
3133
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3134
                                json_object_set_new(event, "started", json_string("ok"));
3135
                                //~ /* Send a FIR */
3136
                                //~ char buf[20];
3137
                                //~ memset(buf, 0, 20);
3138
                                //~ janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
3139
                                //~ JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3140
                                //~ gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
3141
                                //~ /* Send a PLI too, just in case... */
3142
                                //~ memset(buf, 0, 12);
3143
                                //~ janus_rtcp_pli((char *)&buf, 12);
3144
                                //~ JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3145
                                //~ gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
3146
                        } else if(!strcasecmp(request_text, "pause")) {
3147
                                /* Stop receiving the publishers streams for a while */
3148
                                /* TODO */
3149
                                event = json_object();
3150
                                json_object_set_new(event, "videoroom", json_string("event"));
3151
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3152
                                json_object_set_new(event, "paused", json_string("ok"));
3153
                        } else if(!strcasecmp(request_text, "leave")) {
3154
                                /* TODO */
3155
                                event = json_object();
3156
                                json_object_set_new(event, "videoroom", json_string("event"));
3157
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3158
                                json_object_set_new(event, "left", json_string("ok"));
3159
                                session->started = FALSE;
3160
                        } else {
3161
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
3162
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
3163
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
3164
                                goto error;
3165
                        }
3166
                }
3167

    
3168
                /* Prepare JSON event */
3169
                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3170
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3171
                json_decref(event);
3172
                /* Any SDP to handle? */
3173
                if(!msg->sdp) {
3174
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
3175
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3176
                } else {
3177
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
3178
                        const char *type = NULL;
3179
                        if(!strcasecmp(msg->sdp_type, "offer")) {
3180
                                /* We need to answer */
3181
                                type = "answer";
3182
                        } else if(!strcasecmp(msg->sdp_type, "answer")) {
3183
                                /* We got an answer (from a listener?), no need to negotiate */
3184
                                g_atomic_int_set(&session->hangingup, 0);
3185
                                int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
3186
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3187
                                g_free(event_text);
3188
                                root = NULL;
3189
                                janus_videoroom_message_free(msg);
3190
                                continue;
3191
                        } else {
3192
                                /* TODO We don't support anything else right now... */
3193
                                JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg->sdp_type);
3194
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3195
                                g_snprintf(error_cause, 512, "Unknown SDP type '%s'", msg->sdp_type);
3196
                                goto error;
3197
                        }
3198
                        if(session->participant_type != janus_videoroom_p_type_publisher) {
3199
                                /* We shouldn't be here, we always offer ourselves */
3200
                                JANUS_LOG(LOG_ERR, "Only publishers send offers\n");
3201
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3202
                                g_snprintf(error_cause, 512, "Only publishers send offers");
3203
                                goto error;
3204
                        } else {
3205
                                /* This is a new publisher: is there room? */
3206
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
3207
                                janus_videoroom *videoroom = participant->room;
3208
                                int count = 0;
3209
                                GHashTableIter iter;
3210
                                gpointer value;
3211
                                if(!videoroom) {
3212
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3213
                                        goto error;
3214
                                }
3215
                                if(videoroom->destroyed) {
3216
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3217
                                        goto error;
3218
                                }
3219
                                janus_mutex_lock(&videoroom->participants_mutex);
3220
                                g_hash_table_iter_init(&iter, videoroom->participants);
3221
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3222
                                        janus_videoroom_participant *p = value;
3223
                                        if(p != participant && p->sdp)
3224
                                                count++;
3225
                                }
3226
                                janus_mutex_unlock(&videoroom->participants_mutex);
3227
                                if(count == videoroom->max_publishers) {
3228
                                        participant->audio_active = FALSE;
3229
                                        participant->video_active = FALSE;
3230
                                        JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
3231
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3232
                                        g_snprintf(error_cause, 512, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
3233
                                        goto error;
3234
                                }
3235
                                /* Now prepare the SDP to give back */
3236
                                if(strstr(msg->sdp, "Mozilla")) {
3237
                                        participant->firefox = TRUE;
3238
                                }
3239
                                /* Which media are available? */
3240
                                int audio = 0, video = 0, data = 0;
3241
                                const char *audio_mode = NULL, *video_mode = NULL;
3242
                                sdp_parser_t *parser = sdp_parse(sdphome, msg->sdp, strlen(msg->sdp), 0);
3243
                                sdp_session_t *parsed_sdp = sdp_session(parser);
3244
                                if(!parsed_sdp) {
3245
                                        /* Invalid SDP */
3246
                                        JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdp_parsing_error(parser));
3247
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3248
                                        g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdp_parsing_error(parser));
3249
                                        sdp_parser_free(parser);
3250
                                        goto error;
3251
                                }
3252
                                sdp_media_t *m = parsed_sdp->sdp_media;
3253
                                while(m) {
3254
                                        if(m->m_type == sdp_media_audio && m->m_port > 0) {
3255
                                                audio++;
3256
                                                participant->audio = TRUE;
3257
                                                if(audio > 1) {
3258
                                                        m = m->m_next;
3259
                                                        continue;
3260
                                                }
3261
                                        } else if(m->m_type == sdp_media_video && m->m_port > 0) {
3262
                                                video++;
3263
                                                participant->video = TRUE;
3264
                                                if(video > 1) {
3265
                                                        m = m->m_next;
3266
                                                        continue;
3267
                                                }
3268
#ifdef HAVE_SCTP
3269
                                        } else if(m->m_type == sdp_media_application && m->m_port > 0) {
3270
                                                data++;
3271
                                                participant->data = TRUE;
3272
                                                if(data > 1) {
3273
                                                        m = m->m_next;
3274
                                                        continue;
3275
                                                }
3276
#endif
3277
                                        }
3278
                                        if(m->m_type != sdp_media_application) {
3279
                                                /* What is the direction? */
3280
                                                switch(m->m_mode) {
3281
                                                        case sdp_recvonly:
3282
                                                                /* If we're getting a 'recvonly' publisher, we're going to answer with 'inactive' */
3283
                                                        case sdp_inactive:
3284
                                                                if(m->m_type == sdp_media_audio) {
3285
                                                                        audio_mode = "inactive";
3286
                                                                } else {
3287
                                                                        video_mode = "inactive";
3288
                                                                }
3289
                                                                break;
3290
                                                        case sdp_sendonly:
3291
                                                                /* What we expect, turn this into 'recvonly' */
3292
                                                        case sdp_sendrecv:
3293
                                                        default:
3294
                                                                if(m->m_type == sdp_media_audio) {
3295
                                                                        audio_mode = "recvonly";
3296
                                                                } else {
3297
                                                                        video_mode = "recvonly";
3298
                                                                }
3299
                                                                break;
3300
                                                }
3301
                                        }
3302
                                        m = m->m_next;
3303
                                }
3304
                                sdp_parser_free(parser);
3305
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", audio ? "is" : "is NOT");
3306
                                int opus_pt = 0, isac32_pt = 0, isac16_pt = 0, pcmu_pt = 0, pcma_pt = 0,
3307
                                        vp8_pt = 0, vp9_pt = 0, h264_pt = 0;
3308
                                if(audio) {
3309
                                        JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", audio_mode);
3310
                                        opus_pt = janus_get_opus_pt(msg->sdp);
3311
                                        if(opus_pt > 0) {
3312
                                                JANUS_LOG(LOG_VERB, "  -- -- Opus payload type is %d\n", opus_pt);
3313
                                        }
3314
                                        isac32_pt = janus_get_isac32_pt(msg->sdp);
3315
                                        if(isac32_pt > 0) {
3316
                                                JANUS_LOG(LOG_VERB, "  -- -- ISAC 32K payload type is %d\n", isac32_pt);
3317
                                        }
3318
                                        isac16_pt = janus_get_isac16_pt(msg->sdp);
3319
                                        if(isac16_pt > 0) {
3320
                                                JANUS_LOG(LOG_VERB, "  -- -- ISAC 16K payload type is %d\n", isac16_pt);
3321
                                        }
3322
                                        pcmu_pt = janus_get_pcmu_pt(msg->sdp);
3323
                                        if(pcmu_pt > 0) {
3324
                                                JANUS_LOG(LOG_VERB, "  -- -- PCMU payload type is %d\n", pcmu_pt);
3325
                                        }
3326
                                        pcma_pt = janus_get_pcma_pt(msg->sdp);
3327
                                        if(pcma_pt > 0) {
3328
                                                JANUS_LOG(LOG_VERB, "  -- -- PCMA payload type is %d\n", pcma_pt);
3329
                                        }
3330
                                }
3331
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", video ? "is" : "is NOT");
3332
                                if(video) {
3333
                                        JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", video_mode);
3334
                                        vp8_pt = janus_get_vp8_pt(msg->sdp);
3335
                                        if(vp8_pt > 0) {
3336
                                                JANUS_LOG(LOG_VERB, "  -- -- VP8 payload type is %d\n", vp8_pt);
3337
                                        }
3338
                                        vp9_pt = janus_get_vp9_pt(msg->sdp);
3339
                                        if(vp9_pt > 0) {
3340
                                                JANUS_LOG(LOG_VERB, "  -- -- VP9 payload type is %d\n", vp9_pt);
3341
                                        }
3342
                                        h264_pt = janus_get_h264_pt(msg->sdp);
3343
                                        if(h264_pt > 0) {
3344
                                                JANUS_LOG(LOG_VERB, "  -- -- H264 payload type is %d\n", h264_pt);
3345
                                        }
3346
                                }
3347
                                JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", data ? "is" : "is NOT");
3348
                                /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
3349
                                int b = 0;
3350
                                if(participant->firefox)        /* Don't add any b=AS attribute for Chrome */
3351
                                        b = (int)(videoroom->bitrate/1000);
3352
                                char sdp[1280], audio_mline[256], video_mline[512], data_mline[256];
3353
                                if(audio) {
3354
                                        switch(videoroom->acodec) {
3355
                                                case JANUS_VIDEOROOM_OPUS:
3356
                                                        if(opus_pt < 0) {
3357
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing OPUS, but publisher didn't offer any... rejecting audio\n");
3358
                                                                g_snprintf(audio_mline, 256, "m=audio 111 RTP/SAVPF 0\r\n");
3359
                                                        } else {
3360
                                                                g_snprintf(audio_mline, 256, sdp_a_template_opus,
3361
                                                                        opus_pt,                                                /* Opus payload type */
3362
                                                                        audio_mode,                                                /* The publisher gets a recvonly or inactive back */
3363
                                                                        opus_pt);                                                 /* Opus payload type */
3364
                                                        }
3365
                                                        break;
3366
                                                case JANUS_VIDEOROOM_ISAC_32K:
3367
                                                        if(isac32_pt < 0) {
3368
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing ISAC 32K, but publisher didn't offer any... rejecting audio\n");
3369
                                                                g_snprintf(audio_mline, 256, "m=audio 104 RTP/SAVPF 0\r\n");
3370
                                                        } else {
3371
                                                                g_snprintf(audio_mline, 256, sdp_a_template_isac32,
3372
                                                                        isac32_pt,                                                /* ISAC 32K payload type */
3373
                                                                        audio_mode,                                                /* The publisher gets a recvonly or inactive back */
3374
                                                                        isac32_pt);                                         /* ISAC 32K payload type */
3375
                                                        }
3376
                                                        break;
3377
                                                case JANUS_VIDEOROOM_ISAC_16K:
3378
                                                        if(isac16_pt < 0) {
3379
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing ISAC 16K, but publisher didn't offer any... rejecting audio\n");
3380
                                                                g_snprintf(audio_mline, 256, "m=audio 103 RTP/SAVPF 0\r\n");
3381
                                                        } else {
3382
                                                                g_snprintf(audio_mline, 256, sdp_a_template_isac16,
3383
                                                                        isac16_pt,                                                /* ISAC 16K payload type */
3384
                                                                        audio_mode,                                                /* The publisher gets a recvonly or inactive back */
3385
                                                                        isac16_pt);                                                /* ISAC 16K payload type */
3386
                                                        }
3387
                                                        break;
3388
                                                case JANUS_VIDEOROOM_PCMU:
3389
                                                        if(pcmu_pt < 0) {
3390
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing PCMU, but publisher didn't offer any... rejecting audio\n");
3391
                                                                g_snprintf(audio_mline, 256, "m=audio 0 RTP/SAVPF 0\r\n");
3392
                                                        } else {
3393
                                                                g_snprintf(audio_mline, 256, sdp_a_template_pcmu,
3394
                                                                        pcmu_pt,                                                /* PCMU payload type */
3395
                                                                        audio_mode,                                                /* The publisher gets a recvonly or inactive back */
3396
                                                                        pcmu_pt);                                                /* PCMU payload type */
3397
                                                        }
3398
                                                        break;
3399
                                                case JANUS_VIDEOROOM_PCMA:
3400
                                                        if(pcma_pt < 0) {
3401
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing PCMA, but publisher didn't offer any... rejecting audio\n");
3402
                                                                g_snprintf(audio_mline, 256, "m=audio 0 RTP/SAVPF 0\r\n");
3403
                                                        } else {
3404
                                                                g_snprintf(audio_mline, 256, sdp_a_template_pcma,
3405
                                                                        pcma_pt,                                                /* PCMA payload type */
3406
                                                                        audio_mode,                                                /* The publisher gets a recvonly or inactive back */
3407
                                                                        pcma_pt);                                                /* PCMA payload type */
3408
                                                        }
3409
                                                        break;
3410
                                                default:
3411
                                                        /* Shouldn't happen */
3412
                                                        break;
3413
                                        }
3414
                                } else {
3415
                                        audio_mline[0] = '\0';
3416
                                }
3417
                                if(video) {
3418
                                        switch(videoroom->vcodec) {
3419
                                                case JANUS_VIDEOROOM_VP8:
3420
                                                        if(vp8_pt < 0) {
3421
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing VP8, but publisher didn't offer any... rejecting video\n");
3422
                                                                g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
3423
                                                        } else {
3424
                                                                g_snprintf(video_mline, 512, sdp_v_template_vp8,
3425
                                                                        vp8_pt,                                                        /* VP8 payload type */
3426
                                                                        b,                                                                /* Bandwidth */
3427
                                                                        video_mode,                                                /* The publisher gets a recvonly or inactive back */
3428
                                                                        vp8_pt,                                                 /* VP8 payload type */
3429
                                                                        vp8_pt,                                                 /* VP8 payload type */
3430
                                                                        vp8_pt,                                                 /* VP8 payload type */
3431
                                                                        vp8_pt,                                                 /* VP8 payload type */
3432
                                                                        vp8_pt);                                                 /* VP8 payload type */
3433
                                                        }
3434
                                                        break;
3435
                                                case JANUS_VIDEOROOM_VP9:
3436
                                                        if(vp9_pt < 0) {
3437
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing VP9, but publisher didn't offer any... rejecting video\n");
3438
                                                                g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
3439
                                                        } else {
3440
                                                                g_snprintf(video_mline, 512, sdp_v_template_vp9,
3441
                                                                        vp9_pt,                                                        /* VP9 payload type */
3442
                                                                        b,                                                                /* Bandwidth */
3443
                                                                        video_mode,                                                /* The publisher gets a recvonly or inactive back */
3444
                                                                        vp9_pt,                                                 /* VP9 payload type */
3445
                                                                        vp9_pt,                                                 /* VP9 payload type */
3446
                                                                        vp9_pt,                                                 /* VP9 payload type */
3447
                                                                        vp9_pt,                                                 /* VP9 payload type */
3448
                                                                        vp9_pt);                                                 /* VP9 payload type */
3449
                                                        }
3450
                                                        break;
3451
                                                case JANUS_VIDEOROOM_H264:
3452
                                                        if(h264_pt < 0) {
3453
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing H264, but publisher didn't offer any... rejecting video\n");
3454
                                                                g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
3455
                                                        } else {
3456
                                                                g_snprintf(video_mline, 512, sdp_v_template_h264,
3457
                                                                        h264_pt,                                                /* H264 payload type */
3458
                                                                        b,                                                                /* Bandwidth */
3459
                                                                        video_mode,                                                /* The publisher gets a recvonly or inactive back */
3460
                                                                        h264_pt,                                                 /* H264 payload type */
3461
                                                                        h264_pt,                                                 /* H264 payload type */
3462
                                                                        h264_pt,                                                 /* H264 payload type */
3463
                                                                        h264_pt,                                                 /* H264 payload type */
3464
                                                                        h264_pt,                                                 /* H264 payload type */
3465
                                                                        h264_pt);                                                 /* H264 payload type */
3466
                                                        }
3467
                                                        break;
3468
                                                default:
3469
                                                        /* Shouldn't happen */
3470
                                                        break;
3471
                                        }
3472
                                } else {
3473
                                        video_mline[0] = '\0';
3474
                                }
3475
                                if(data) {
3476
                                        g_snprintf(data_mline, 256, sdp_d_template);
3477
                                } else {
3478
                                        data_mline[0] = '\0';
3479
                                }
3480
                                g_snprintf(sdp, 1280, sdp_template,
3481
                                        janus_get_real_time(),                        /* We need current time here */
3482
                                        janus_get_real_time(),                        /* We need current time here */
3483
                                        participant->room->room_name,        /* Video room name */
3484
                                        audio_mline,                                        /* Audio m-line, if any */
3485
                                        video_mline,                                        /* Video m-line, if any */
3486
                                        data_mline);                                        /* Data channel m-line, if any */
3487

    
3488
                                char *newsdp = g_strdup(sdp);
3489
                                if(video && b == 0) {
3490
                                        /* Remove useless bandwidth attribute */
3491
                                        newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
3492
                                }
3493
                                /* Is this room recorded? */
3494
                                if(videoroom->record || participant->recording_active) {
3495
                                        char filename[255];
3496
                                        gint64 now = janus_get_real_time();
3497
                                        if(audio) {
3498
                                                memset(filename, 0, 255);
3499
                                                if(participant->recording_base) {
3500
                                                        /* Use the filename and path we have been provided */
3501
                                                        g_snprintf(filename, 255, "%s-audio", participant->recording_base);
3502
                                                        participant->arc = janus_recorder_create(videoroom->rec_dir, 0, filename);
3503
                                                        if(participant->arc == NULL) {
3504
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
3505
                                                        }
3506
                                                } else {
3507
                                                        /* Build a filename */
3508
                                                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
3509
                                                                videoroom->room_id, participant->user_id, now);
3510
                                                        participant->arc = janus_recorder_create(videoroom->rec_dir, 0, filename);
3511
                                                        if(participant->arc == NULL) {
3512
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
3513
                                                        }
3514
                                                }
3515
                                        }
3516
                                        if(video) {
3517
                                                memset(filename, 0, 255);
3518
                                                if(participant->recording_base) {
3519
                                                        /* Use the filename and path we have been provided */
3520
                                                        g_snprintf(filename, 255, "%s-video", participant->recording_base);
3521
                                                        participant->vrc = janus_recorder_create(videoroom->rec_dir, 1, filename);
3522
                                                        if(participant->vrc == NULL) {
3523
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
3524
                                                        }
3525
                                                } else {
3526
                                                        /* Build a filename */
3527
                                                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
3528
                                                                videoroom->room_id, participant->user_id, now);
3529
                                                        participant->vrc = janus_recorder_create(videoroom->rec_dir, 1, filename);
3530
                                                        if(participant->vrc == NULL) {
3531
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
3532
                                                        }
3533
                                                }
3534
                                        }
3535
                                }
3536

    
3537
                                JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, newsdp);
3538
                                /* How long will the gateway take to push the event? */
3539
                                g_atomic_int_set(&session->hangingup, 0);
3540
                                gint64 start = janus_get_monotonic_time();
3541
                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, newsdp);
3542
                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3543

    
3544
                                /* Now turn the SDP into what we'll send subscribers, using the static payload types for making switching easier */
3545
                                if(audio) {
3546
                                        switch(videoroom->acodec) {
3547
                                                case JANUS_VIDEOROOM_OPUS:
3548
                                                        if(opus_pt < 0) {
3549
                                                                audio_mline[0] = '\0';
3550
                                                        } else {
3551
                                                                g_snprintf(audio_mline, 256, sdp_a_template_opus,
3552
                                                                        OPUS_PT,                                                /* Opus payload type */
3553
                                                                        /* Subscribers gets a sendonly or inactive back */
3554
                                                                        strcmp(audio_mode, "inactive") ? "sendonly" : "inactive",
3555
                                                                        OPUS_PT);                                                 /* Opus payload type */
3556
                                                        }
3557
                                                        break;
3558
                                                case JANUS_VIDEOROOM_ISAC_32K:
3559
                                                        if(isac32_pt < 0 ) {
3560
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing ISAC 32K, but publisher didn't offer any... rejecting audio\n");
3561
                                                                g_snprintf(audio_mline, 256, "m=audio 104 RTP/SAVPF 0\r\n");
3562
                                                        } else {
3563
                                                                g_snprintf(audio_mline, 256, sdp_a_template_isac32,
3564
                                                                        ISAC32_PT,                                                /* ISAC 32K payload type */
3565
                                                                        /* Subscribers gets a sendonly or inactive back */
3566
                                                                        strcmp(audio_mode, "inactive") ? "sendonly" : "inactive",
3567
                                                                        ISAC32_PT);                                                /* ISAC 32K payload type */
3568
                                                        }
3569
                                                        break;
3570
                                                case JANUS_VIDEOROOM_ISAC_16K:
3571
                                                        if(isac16_pt < 0) {
3572
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing ISAC 16K, but publisher didn't offer any... rejecting audio\n");
3573
                                                                g_snprintf(audio_mline, 256, "m=audio 103 RTP/SAVPF 0\r\n");
3574
                                                        } else {
3575
                                                                g_snprintf(audio_mline, 256, sdp_a_template_isac16,
3576
                                                                        ISAC16_PT,                                                /* ISAC 16K payload type */
3577
                                                                        /* Subscribers gets a sendonly or inactive back */
3578
                                                                        strcmp(audio_mode, "inactive") ? "sendonly" : "inactive",
3579
                                                                        ISAC16_PT);                                                /* ISAC 16K payload type */
3580
                                                        }
3581
                                                        break;
3582
                                                case JANUS_VIDEOROOM_PCMU:
3583
                                                        if(pcmu_pt < 0) {
3584
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing PCMU, but publisher didn't offer any... rejecting audio\n");
3585
                                                                g_snprintf(audio_mline, 256, "m=audio 0 RTP/SAVPF 0\r\n");
3586
                                                        } else {
3587
                                                                g_snprintf(audio_mline, 256, sdp_a_template_pcmu,
3588
                                                                        PCMU_PT,                                                /*PCMU payload type */
3589
                                                                        /* Subscribers gets a sendonly or inactive back */
3590
                                                                        strcmp(audio_mode, "inactive") ? "sendonly" : "inactive",
3591
                                                                        PCMU_PT);                                                 /*PCMU   payload type */
3592
                                                        }
3593
                                                        break;
3594
                                                case JANUS_VIDEOROOM_PCMA:
3595
                                                        if(pcma_pt < 0) {
3596
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing PCMA, but publisher didn't offer any... rejecting audio\n");
3597
                                                                g_snprintf(audio_mline, 256, "m=audio 0 RTP/SAVPF 0\r\n");
3598
                                                        } else {
3599
                                                                g_snprintf(audio_mline, 256, sdp_a_template_pcma,
3600
                                                                        PCMA_PT,                                                /*PCMA payload type */
3601
                                                                        /* Subscribers gets a sendonly or inactive back */
3602
                                                                        strcmp(audio_mode, "inactive") ? "sendonly" : "inactive",
3603
                                                                        PCMA_PT);                                                 /*PCMA   payload type */
3604
                                                        }
3605
                                                        break;
3606
                                                default:
3607
                                                        /* Shouldn't happen */
3608
                                                        break;
3609
                                                }
3610
                                } else {
3611
                                        audio_mline[0] = '\0';
3612
                                }
3613
                                if(video) {
3614
                                        switch(videoroom->vcodec) {
3615
                                                case JANUS_VIDEOROOM_VP8:
3616
                                                        if(vp8_pt < 0) {
3617
                                                                video_mline[0] = '\0';
3618
                                                        } else {
3619
                                                                g_snprintf(video_mline, 512, sdp_v_template_vp8,
3620
                                                                        VP8_PT,                                                        /* VP8 payload type */
3621
                                                                        b,                                                                /* Bandwidth */
3622
                                                                        /* Subscribers gets a sendonly or inactive back */
3623
                                                                        strcmp(video_mode, "inactive") ? "sendonly" : "inactive",
3624
                                                                        VP8_PT,                                                 /* VP8 payload type */
3625
                                                                        VP8_PT,                                                 /* VP8 payload type */
3626
                                                                        VP8_PT,                                                 /* VP8 payload type */
3627
                                                                        VP8_PT,                                                 /* VP8 payload type */
3628
                                                                        VP8_PT);                                                 /* VP8 payload type */
3629
                                                        }
3630
                                                        break;
3631
                                                case JANUS_VIDEOROOM_VP9:
3632
                                                        if(vp9_pt < 0) {
3633
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing VP8, but publisher didn't offer any... rejecting video\n");
3634
                                                                g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
3635
                                                        } else {
3636
                                                                g_snprintf(video_mline, 512, sdp_v_template_vp9,
3637
                                                                        VP9_PT,                                                        /* VP9 payload type */
3638
                                                                        b,                                                                /* Bandwidth */
3639
                                                                        /* Subscribers gets a sendonly or inactive back */
3640
                                                                        strcmp(video_mode, "inactive") ? "sendonly" : "inactive",
3641
                                                                        VP9_PT,                                                 /* VP9 payload type */
3642
                                                                        VP9_PT,                                                 /* VP9 payload type */
3643
                                                                        VP9_PT,                                                 /* VP9 payload type */
3644
                                                                        VP9_PT,                                                 /* VP9 payload type */
3645
                                                                        VP9_PT);                                                 /* VP9 payload type */
3646
                                                        }
3647
                                                        break;
3648
                                                case JANUS_VIDEOROOM_H264:
3649
                                                        if(h264_pt < 0) {
3650
                                                                JANUS_LOG(LOG_WARN, "Videoroom is forcing VP8, but publisher didn't offer any... rejecting video\n");
3651
                                                                g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
3652
                                                        } else {
3653
                                                                g_snprintf(video_mline, 512, sdp_v_template_h264,
3654
                                                                        H264_PT,                                                /* H264 payload type */
3655
                                                                        b,                                                                /* Bandwidth */
3656
                                                                        /* Subscribers gets a sendonly or inactive back */
3657
                                                                        strcmp(video_mode, "inactive") ? "sendonly" : "inactive",
3658
                                                                        H264_PT,                                                 /* H264 payload type */
3659
                                                                        H264_PT,                                                 /* H264 payload type */
3660
                                                                        H264_PT,                                                 /* H264 payload type */
3661
                                                                        H264_PT,                                                 /* H264 payload type */
3662
                                                                        H264_PT,                                                 /* H264 payload type */
3663
                                                                        H264_PT);                                                 /* H264 payload type */
3664
                                                        }
3665
                                                        break;
3666
                                                default:
3667
                                                        /* Shouldn't happen */
3668
                                                        break;
3669
                                        }
3670
                                } else {
3671
                                        video_mline[0] = '\0';
3672
                                }
3673
                                if(data) {
3674
                                        g_snprintf(data_mline, 256, sdp_d_template);
3675
                                } else {
3676
                                        data_mline[0] = '\0';
3677
                                }
3678
                                g_snprintf(sdp, 1280, sdp_template,
3679
                                        janus_get_real_time(),                        /* We need current time here */
3680
                                        janus_get_real_time(),                        /* We need current time here */
3681
                                        participant->room->room_name,        /* Video room name */
3682
                                        audio_mline,                                        /* Audio m-line, if any */
3683
                                        video_mline,                                        /* Video m-line, if any */
3684
                                        data_mline);                                        /* Data channel m-line, if any */
3685
                                g_free(newsdp);
3686
                                newsdp = g_strdup(sdp);
3687
                                if(video && b == 0) {
3688
                                        /* Remove useless bandwidth attribute */
3689
                                        newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
3690
                                }
3691

    
3692
                                /* Done */
3693
                                if(res != JANUS_OK) {
3694
                                        /* TODO Failed to negotiate? We should remove this publisher */
3695
                                } else {
3696
                                        /* Store the participant's SDP for interested listeners */
3697
                                        participant->sdp = newsdp;
3698
                                        /* Notify all other participants that there's a new boy in town */
3699
                                        json_t *list = json_array();
3700
                                        json_t *pl = json_object();
3701
                                        json_object_set_new(pl, "id", json_integer(participant->user_id));
3702
                                        if(participant->display)
3703
                                                json_object_set_new(pl, "display", json_string(participant->display));
3704
                                        json_array_append_new(list, pl);
3705
                                        json_t *pub = json_object();
3706
                                        json_object_set_new(pub, "videoroom", json_string("event"));
3707
                                        json_object_set_new(pub, "room", json_integer(participant->room->room_id));
3708
                                        json_object_set_new(pub, "publishers", list);
3709
                                        char *pub_text = json_dumps(pub, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3710
                                        json_decref(pub);
3711
                                        GHashTableIter iter;
3712
                                        gpointer value;
3713
                                        janus_mutex_lock(&videoroom->participants_mutex);
3714
                                        g_hash_table_iter_init(&iter, videoroom->participants);
3715
                                        while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3716
                                                janus_videoroom_participant *p = value;
3717
                                                if(p == participant) {
3718
                                                        continue;        /* Skip the new publisher itself */
3719
                                                }
3720
                                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
3721
                                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub_text, NULL, NULL);
3722
                                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3723
                                        }
3724
                                        g_free(pub_text);
3725
                                        janus_mutex_unlock(&videoroom->participants_mutex);
3726
                                        /* Let's wait for the setup_media event */
3727
                                }
3728
                        }
3729
                }
3730
                g_free(event_text);
3731
                janus_videoroom_message_free(msg);
3732

    
3733
                continue;
3734
                
3735
error:
3736
                {
3737
                        /* Prepare JSON error event */
3738
                        json_t *event = json_object();
3739
                        json_object_set_new(event, "videoroom", json_string("event"));
3740
                        json_object_set_new(event, "error_code", json_integer(error_code));
3741
                        json_object_set_new(event, "error", json_string(error_cause));
3742
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3743
                        json_decref(event);
3744
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
3745
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
3746
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3747
                        g_free(event_text);
3748
                        janus_videoroom_message_free(msg);
3749
                }
3750
        }
3751
        JANUS_LOG(LOG_VERB, "Leaving VideoRoom handler thread\n");
3752
        return NULL;
3753
}
3754

    
3755

    
3756
/* Multiplexing helpers */
3757
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
3758
        if(!muxed_listener || !feeds)
3759
                return -1;
3760
        janus_mutex_lock(&muxed_listener->listeners_mutex);
3761
        JANUS_LOG(LOG_VERB, "Subscribing to %d feeds\n", g_list_length(feeds));
3762
        janus_videoroom *videoroom = muxed_listener->room;
3763
        GList *ps = feeds;
3764
        json_t *list = json_array();
3765
        int added_feeds = 0;
3766
        while(ps) {
3767
                uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
3768
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
3769
                if(publisher == NULL) { //~ || publisher->sdp == NULL) {
3770
                        /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
3771
                        JANUS_LOG(LOG_WARN, "No such feed (%"SCNu64"), skipping\n", feed_id);
3772
                        ps = ps->next;
3773
                        continue;
3774
                }
3775
                /* Are we already subscribed? */
3776
                gboolean subscribed = FALSE;
3777
                GSList *ls = muxed_listener->listeners;
3778
                while(ls) {
3779
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ls->data;
3780
                        if(l && (l->feed == publisher)) {
3781
                                subscribed = TRUE;
3782
                                JANUS_LOG(LOG_WARN, "Already subscribed to feed %"SCNu64", skipping\n", feed_id);
3783
                                break;
3784
                        }
3785
                        ls = ls->next;
3786
                }
3787
                if(subscribed) {
3788
                        ps = ps->next;
3789
                        continue;
3790
                }
3791
                janus_videoroom_listener *listener = g_malloc0(sizeof(janus_videoroom_listener));
3792
                if(listener == NULL) {
3793
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
3794
                        ps = ps->next;
3795
                        continue;
3796
                }
3797
                listener->session = muxed_listener->session;
3798
                listener->room = videoroom;
3799
                listener->feed = publisher;
3800
                //~ listener->paused = TRUE;        /* We need an explicit start from the listener */
3801
                listener->paused = FALSE;
3802
                listener->parent = muxed_listener;
3803
                janus_mutex_lock(&publisher->listeners_mutex);
3804