Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ a972337c

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

    
124
#include "plugin.h"
125

    
126
#include <jansson.h>
127
#include <sofia-sip/sdp.h>
128

    
129
#include "../debug.h"
130
#include "../apierror.h"
131
#include "../config.h"
132
#include "../mutex.h"
133
#include "../rtp.h"
134
#include "../rtcp.h"
135
#include "../record.h"
136
#include "../utils.h"
137
#include <sys/types.h>
138
#include <sys/socket.h>
139

    
140

    
141
/* Plugin information */
142
#define JANUS_VIDEOROOM_VERSION                        7
143
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.7"
144
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing SFU (Selective Forwarding Unit) for Janus, that is an audio/video router."
145
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
146
#define JANUS_VIDEOROOM_AUTHOR                        "Meetecho s.r.l."
147
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
148

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

    
171
/* Plugin setup */
172
static janus_plugin janus_videoroom_plugin =
173
        JANUS_PLUGIN_INIT (
174
                .init = janus_videoroom_init,
175
                .destroy = janus_videoroom_destroy,
176

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

    
197
/* Plugin creator */
198
janus_plugin *create(void) {
199
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
200
        return &janus_videoroom_plugin;
201
}
202

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

    
279
/* Static configuration instance */
280
static janus_config *config = NULL;
281
static const char *config_folder = NULL;
282
static janus_mutex config_mutex;
283

    
284
/* Useful stuff */
285
static volatile gint initialized = 0, stopping = 0;
286
static janus_callbacks *gateway = NULL;
287
static GThread *handler_thread;
288
static GThread *watchdog;
289
static su_home_t *sdphome = NULL;
290
static void *janus_videoroom_handler(void *data);
291
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
292
static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data);
293

    
294
typedef enum janus_videoroom_p_type {
295
        janus_videoroom_p_type_none = 0,
296
        janus_videoroom_p_type_subscriber,                        /* Generic listener/subscriber */
297
        janus_videoroom_p_type_subscriber_muxed,        /* Multiplexed listener/subscriber */
298
        janus_videoroom_p_type_publisher,                        /* Participant/publisher */
299
} janus_videoroom_p_type;
300

    
301
typedef struct janus_videoroom_message {
302
        janus_plugin_session *handle;
303
        char *transaction;
304
        json_t *message;
305
        json_t *jsep;
306
} janus_videoroom_message;
307
static GAsyncQueue *messages = NULL;
308
static janus_videoroom_message exit_message;
309

    
310
static void janus_videoroom_message_free(janus_videoroom_message *msg) {
311
        if(!msg || msg == &exit_message)
312
                return;
313

    
314
        msg->handle = NULL;
315

    
316
        g_free(msg->transaction);
317
        msg->transaction = NULL;
318
        if(msg->message)
319
                json_decref(msg->message);
320
        msg->message = NULL;
321
        if(msg->jsep)
322
                json_decref(msg->jsep);
323
        msg->jsep = NULL;
324

    
325
        g_free(msg);
326
}
327

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

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

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

    
395
typedef struct janus_videoroom_session {
396
        janus_plugin_session *handle;
397
        janus_videoroom_p_type participant_type;
398
        gpointer participant;
399
        gboolean started;
400
        gboolean stopping;
401
        volatile gint hangingup;
402
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
403
} janus_videoroom_session;
404
static GHashTable *sessions;
405
static GList *old_sessions;
406
static janus_mutex sessions_mutex;
407

    
408
/* a host whose ports gets streamed rtp packets of the corresponding type. */
409
typedef struct rtp_forwarder {
410
        int is_video;
411
        struct sockaddr_in serv_addr;
412
} rtp_forwarder;
413

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

    
456
typedef struct janus_videoroom_listener {
457
        janus_videoroom_session *session;
458
        janus_videoroom *room;        /* Room */
459
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
460
        janus_videoroom_listener_context context;        /* Needed in case there are publisher switches on this listener */
461
        gboolean audio, video, data;                /* Whether audio, video and/or data must be sent to this publisher */
462
        struct janus_videoroom_listener_muxed *parent;        /* Overall subscriber, if this is a sub-listener in a Multiplexed one */
463
        gboolean paused;
464
} janus_videoroom_listener;
465
static void janus_videoroom_listener_free(janus_videoroom_listener *l);
466

    
467
typedef struct janus_videoroom_listener_muxed {
468
        janus_videoroom_session *session;
469
        janus_videoroom *room;        /* Room */
470
        GSList *listeners;        /* List of listeners (as a Multiplexed listener can be subscribed to more publishers at the same time) */
471
        janus_mutex listeners_mutex;
472
} janus_videoroom_listener_muxed;
473
static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l);
474

    
475
typedef struct janus_videoroom_rtp_relay_packet {
476
        rtp_header *data;
477
        gint length;
478
        gint is_video;
479
        uint32_t timestamp;
480
        uint16_t seq_number;
481
} janus_videoroom_rtp_relay_packet;
482

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

    
565

    
566
/* Error codes */
567
#define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR                499
568
#define JANUS_VIDEOROOM_ERROR_NO_MESSAGE                421
569
#define JANUS_VIDEOROOM_ERROR_INVALID_JSON                422
570
#define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST        423
571
#define JANUS_VIDEOROOM_ERROR_JOIN_FIRST                424
572
#define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED        425
573
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM                426
574
#define JANUS_VIDEOROOM_ERROR_ROOM_EXISTS                427
575
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED                428
576
#define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT        429
577
#define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT        430
578
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE        431
579
#define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL        432
580
#define JANUS_VIDEOROOM_ERROR_UNAUTHORIZED                433
581
#define JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED        434
582
#define JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED                435
583
#define JANUS_VIDEOROOM_ERROR_ID_EXISTS                        436
584
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP                437
585

    
586

    
587
/* Multiplexing helpers */
588
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
589
int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
590
int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, json_t *event);
591

    
592
static guint32 janus_rtp_forwarder_add_helper(janus_videoroom_participant *p, const gchar* host, int port, int is_video) {
593
        if(!p || !host) {
594
                return 0;
595
        }
596
        rtp_forwarder *forward = g_malloc0(sizeof(rtp_forwarder));
597
        forward->is_video = is_video;
598
        forward->serv_addr.sin_family = AF_INET;
599
        inet_pton(AF_INET, host, &(forward->serv_addr.sin_addr));
600
        forward->serv_addr.sin_port = htons(port);
601
        janus_mutex_lock(&p->rtp_forwarders_mutex);
602
        guint32 stream_id = janus_random_uint32();
603
        while(g_hash_table_lookup(p->rtp_forwarders, GUINT_TO_POINTER(stream_id)) != NULL) {
604
                stream_id = janus_random_uint32();
605
        }
606
        g_hash_table_insert(p->rtp_forwarders, GUINT_TO_POINTER(stream_id), forward);
607
        janus_mutex_unlock(&p->rtp_forwarders_mutex);
608
        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);
609
        return stream_id;
610
}
611

    
612

    
613
/* Convenience function for freeing a session */
614
static void session_free(gpointer data) {
615
        if(data) {
616
                janus_videoroom_session* session = (janus_videoroom_session*)data;
617
                switch(session->participant_type) {
618
                case janus_videoroom_p_type_publisher: 
619
                        janus_videoroom_participant_free(session->participant);
620
                        break;   
621
                case janus_videoroom_p_type_subscriber:
622
                        janus_videoroom_listener_free(session->participant);
623
                        break;
624
                case janus_videoroom_p_type_subscriber_muxed:
625
                        janus_videoroom_muxed_listener_free(session->participant);
626
                        break;
627
                default:
628
                        break;
629
                }
630
                session->handle = NULL;
631
                g_free(session);
632
                session = NULL;
633
        }
634
}
635

    
636
static void janus_rtp_forwarder_free_helper(gpointer data) {
637
        if(data) {
638
                rtp_forwarder* forward = (rtp_forwarder*)data;
639
                if(forward) {
640
                        g_free(forward);
641
                        forward = NULL;
642
                }
643
        }
644
}
645

    
646
/* Convenience wrapper function for session_free that corresponds to GHRFunc() format for hash table cleanup */
647
static gboolean session_hash_table_remove(gpointer key, gpointer value, gpointer not_used) {
648
        if(value) {
649
                session_free(value);
650
        }
651
        return TRUE;
652
}
653

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

    
720

    
721
/* Plugin implementation */
722
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
723
        if(g_atomic_int_get(&stopping)) {
724
                /* Still stopping from before */
725
                return -1;
726
        }
727
        if(callback == NULL || config_path == NULL) {
728
                /* Invalid arguments */
729
                return -1;
730
        }
731
        sdphome = su_home_new(sizeof(su_home_t));
732
        if(su_home_init(sdphome) < 0) {
733
                JANUS_LOG(LOG_FATAL, "Ops, error setting up sofia-sdp?\n");
734
                return -1;
735
        }
736

    
737
        /* Read configuration */
738
        char filename[255];
739
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
740
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
741
        config = janus_config_parse(filename);
742
        config_folder = config_path;
743
        if(config != NULL)
744
                janus_config_print(config);
745
        janus_mutex_init(&config_mutex);
746

    
747
        rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal,
748
                (GDestroyNotify)g_free, (GDestroyNotify) janus_videoroom_free);
749
        janus_mutex_init(&rooms_mutex);
750
        sessions = g_hash_table_new(NULL, NULL);
751
        janus_mutex_init(&sessions_mutex);
752

    
753
        messages = g_async_queue_new_full((GDestroyNotify) janus_videoroom_message_free);
754

    
755
        /* This is the callback we'll need to invoke to contact the gateway */
756
        gateway = callback;
757

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

    
870
        /* Show available rooms */
871
        janus_mutex_lock(&rooms_mutex);
872
        GHashTableIter iter;
873
        gpointer value;
874
        g_hash_table_iter_init(&iter, rooms);
875
        while (g_hash_table_iter_next(&iter, NULL, &value)) {
876
                janus_videoroom *vr = value;
877
                JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds, %s audio codec, %s video codec\n",
878
                        vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq,
879
                        janus_videoroom_audiocodec_name(vr->acodec), janus_videoroom_videocodec_name(vr->vcodec));
880
        }
881
        janus_mutex_unlock(&rooms_mutex);
882

    
883
        g_atomic_int_set(&initialized, 1);
884

    
885
        GError *error = NULL;
886
        /* Start the sessions watchdog */
887
        watchdog = g_thread_try_new("videoroom watchdog", &janus_videoroom_watchdog, NULL, &error);
888
        if(error != NULL) {
889
                g_atomic_int_set(&initialized, 0);
890
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom watchdog thread...\n", error->code, error->message ? error->message : "??");
891
                janus_config_destroy(config);
892
                return -1;
893
        }
894
        /* Launch the thread that will handle incoming messages */
895
        handler_thread = g_thread_try_new("videoroom handler", janus_videoroom_handler, NULL, &error);
896
        if(error != NULL) {
897
                g_atomic_int_set(&initialized, 0);
898
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom handler thread...\n", error->code, error->message ? error->message : "??");
899
                janus_config_destroy(config);
900
                return -1;
901
        }
902
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
903
        return 0;
904
}
905

    
906
void janus_videoroom_destroy(void) {
907
        if(!g_atomic_int_get(&initialized))
908
                return;
909
        g_atomic_int_set(&stopping, 1);
910

    
911
        g_async_queue_push(messages, &exit_message);
912
        if(handler_thread != NULL) {
913
                g_thread_join(handler_thread);
914
                handler_thread = NULL;
915
        }
916
        if(watchdog != NULL) {
917
                g_thread_join(watchdog);
918
                watchdog = NULL;
919
        }
920
        su_home_deinit(sdphome);
921
        su_home_unref(sdphome);
922
        sdphome = NULL;
923

    
924
        /* FIXME We should destroy the sessions cleanly */
925
        janus_mutex_lock(&sessions_mutex);
926
        g_hash_table_foreach_remove(sessions, (GHRFunc)session_hash_table_remove, NULL);
927
        g_hash_table_destroy(sessions);
928
        sessions = NULL;
929
        janus_mutex_unlock(&sessions_mutex);
930

    
931
        janus_mutex_lock(&rooms_mutex);
932

    
933
        g_hash_table_destroy(rooms);
934
        rooms = NULL;
935
        janus_mutex_unlock(&rooms_mutex);
936
        janus_mutex_destroy(&rooms_mutex);
937

    
938
        g_async_queue_unref(messages);
939
        messages = NULL;
940

    
941
        janus_config_destroy(config);
942
        g_free(admin_key);
943

    
944
        g_atomic_int_set(&initialized, 0);
945
        g_atomic_int_set(&stopping, 0);
946
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
947
}
948

    
949
int janus_videoroom_get_api_compatibility(void) {
950
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
951
        return JANUS_PLUGIN_API_VERSION;
952
}
953

    
954
int janus_videoroom_get_version(void) {
955
        return JANUS_VIDEOROOM_VERSION;
956
}
957

    
958
const char *janus_videoroom_get_version_string(void) {
959
        return JANUS_VIDEOROOM_VERSION_STRING;
960
}
961

    
962
const char *janus_videoroom_get_description(void) {
963
        return JANUS_VIDEOROOM_DESCRIPTION;
964
}
965

    
966
const char *janus_videoroom_get_name(void) {
967
        return JANUS_VIDEOROOM_NAME;
968
}
969

    
970
const char *janus_videoroom_get_author(void) {
971
        return JANUS_VIDEOROOM_AUTHOR;
972
}
973

    
974
const char *janus_videoroom_get_package(void) {
975
        return JANUS_VIDEOROOM_PACKAGE;
976
}
977

    
978
void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
979
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
980
                *error = -1;
981
                return;
982
        }        
983
        janus_videoroom_session *session = (janus_videoroom_session *)g_malloc0(sizeof(janus_videoroom_session));
984
        session->handle = handle;
985
        session->participant_type = janus_videoroom_p_type_none;
986
        session->participant = NULL;
987
        session->destroyed = 0;
988
        g_atomic_int_set(&session->hangingup, 0);
989
        handle->plugin_handle = session;
990
        janus_mutex_lock(&sessions_mutex);
991
        g_hash_table_insert(sessions, handle, session);
992
        janus_mutex_unlock(&sessions_mutex);
993

    
994
        return;
995
}
996

    
997
static void janus_videoroom_notify_participants(janus_videoroom_participant *participant, json_t *msg) {
998
        /* participant->room->participants_mutex has to be locked. */
999
        GHashTableIter iter;
1000
        gpointer value;
1001
        g_hash_table_iter_init(&iter, participant->room->participants);
1002
        while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1003
                janus_videoroom_participant *p = value;
1004
                if(p && p->session && p != participant) {
1005
                        JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1006
                        int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, msg, NULL);
1007
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1008
                }
1009
        }
1010
}
1011

    
1012
static void janus_videoroom_leave_or_unpublish(janus_videoroom_participant *participant, gboolean is_leaving) {
1013
        json_t *event = json_object();
1014
        json_object_set_new(event, "videoroom", json_string("event"));
1015
        json_object_set_new(event, "room", json_integer(participant->room->room_id));
1016
        json_object_set_new(event, is_leaving ? "leaving" : "unpublished", json_integer(participant->user_id));
1017
        /* we need to check if the room still exists, may have been destroyed already */
1018
        if(participant->room) {
1019
                if(!participant->room->destroyed) {
1020
                        janus_mutex_lock(&participant->room->participants_mutex);
1021
                        janus_videoroom_notify_participants(participant, event);
1022
                        if(is_leaving) {
1023
                                g_hash_table_remove(participant->room->participants, &participant->user_id);
1024
                        }
1025
                        janus_mutex_unlock(&participant->room->participants_mutex);
1026
                }
1027
        }
1028
        json_decref(event);
1029
}
1030

    
1031
void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
1032
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1033
                *error = -1;
1034
                return;
1035
        }        
1036
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle; 
1037
        if(!session) {
1038
                JANUS_LOG(LOG_ERR, "No VideoRoom session associated with this handle...\n");
1039
                *error = -2;
1040
                return;
1041
        }
1042
        if(session->destroyed) {
1043
                JANUS_LOG(LOG_WARN, "VideoRoom session already marked as destroyed...\n");
1044
                return;
1045
        }
1046
        JANUS_LOG(LOG_VERB, "Removing VideoRoom session...\n");
1047
        /* Cleaning up and removing the session is done in a lazy way */
1048
        janus_mutex_lock(&sessions_mutex);
1049
        if(!session->destroyed) {
1050
                /* Any related WebRTC PeerConnection is not available anymore either */
1051
                janus_videoroom_hangup_media(handle);
1052
                session->destroyed = janus_get_monotonic_time();
1053
                old_sessions = g_list_append(old_sessions, session);
1054
                if(session->participant_type == janus_videoroom_p_type_publisher) {
1055
                        /* Get rid of publisher */
1056
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1057
                        participant->audio = FALSE;
1058
                        participant->video = FALSE;
1059
                        participant->data = FALSE;
1060
                        participant->audio_active = FALSE;
1061
                        participant->video_active = FALSE;
1062
                        participant->recording_active = FALSE;
1063
                        if(participant->recording_base)
1064
                                g_free(participant->recording_base);
1065
                        participant->recording_base = NULL;
1066
                        janus_videoroom_leave_or_unpublish(participant, TRUE);
1067
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1068
                        /* Detaching this listener from its publisher is already done by hangup_media */
1069
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1070
                        /* Detaching this listener from its publishers is already done by hangup_media */
1071
                }
1072
        }
1073
        janus_mutex_unlock(&sessions_mutex);
1074

    
1075
        return;
1076
}
1077

    
1078
json_t *janus_videoroom_query_session(janus_plugin_session *handle) {
1079
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1080
                return NULL;
1081
        }        
1082
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1083
        if(!session) {
1084
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1085
                return NULL;
1086
        }
1087
        /* Show the participant/room info, if any */
1088
        json_t *info = json_object();
1089
        if(session->participant) {
1090
                if(session->participant_type == janus_videoroom_p_type_none) {
1091
                        json_object_set_new(info, "type", json_string("none"));
1092
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
1093
                        json_object_set_new(info, "type", json_string("publisher"));
1094
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1095
                        if(participant) {
1096
                                janus_videoroom *room = participant->room; 
1097
                                json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1098
                                json_object_set_new(info, "id", json_integer(participant->user_id));
1099
                                if(participant->display)
1100
                                        json_object_set_new(info, "display", json_string(participant->display));
1101
                                if(participant->listeners)
1102
                                        json_object_set_new(info, "viewers", json_integer(g_slist_length(participant->listeners)));
1103
                                json_t *media = json_object();
1104
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1105
                                json_object_set_new(media, "video", json_integer(participant->video));
1106
                                json_object_set_new(media, "data", json_integer(participant->data));
1107
                                json_object_set_new(info, "media", media);
1108
                                json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
1109
                                if(participant->arc || participant->vrc) {
1110
                                        json_t *recording = json_object();
1111
                                        if(participant->arc && participant->arc->filename)
1112
                                                json_object_set_new(recording, "audio", json_string(participant->arc->filename));
1113
                                        if(participant->vrc && participant->vrc->filename)
1114
                                                json_object_set_new(recording, "video", json_string(participant->vrc->filename));
1115
                                        json_object_set_new(info, "recording", recording);
1116
                                }
1117
                        }
1118
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1119
                        json_object_set_new(info, "type", json_string("listener"));
1120
                        janus_videoroom_listener *participant = (janus_videoroom_listener *)session->participant;
1121
                        if(participant) {
1122
                                janus_videoroom_participant *feed = (janus_videoroom_participant *)participant->feed;
1123
                                if(feed) {
1124
                                        janus_videoroom *room = feed->room; 
1125
                                        json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
1126
                                        json_object_set_new(info, "feed_id", json_integer(feed->user_id));
1127
                                        if(feed->display)
1128
                                                json_object_set_new(info, "feed_display", json_string(feed->display));
1129
                                }
1130
                                json_t *media = json_object();
1131
                                json_object_set_new(media, "audio", json_integer(participant->audio));
1132
                                json_object_set_new(media, "video", json_integer(participant->video));
1133
                                json_object_set_new(media, "data", json_integer(participant->data));
1134
                                json_object_set_new(info, "media", media);
1135
                        }
1136
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1137
                        json_object_set_new(info, "type", json_string("muxed-listener"));
1138
                        /* TODO */
1139
                }
1140
        }
1141
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
1142
        return info;
1143
}
1144

    
1145
static int janus_videoroom_access_room(json_t *root, gboolean check_secret, gboolean check_pin, janus_videoroom **videoroom, char *error_cause, int error_cause_size) {
1146
        /* rooms_mutex has to be locked */
1147
        int error_code = 0;
1148
        json_t *room = json_object_get(root, "room");
1149
        guint64 room_id = json_integer_value(room);
1150
        *videoroom = g_hash_table_lookup(rooms, &room_id);
1151
        if(*videoroom == NULL) {
1152
                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1153
                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1154
                if(error_cause)
1155
                        g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
1156
                return error_code;
1157
        }
1158
        if((*videoroom)->destroyed) {
1159
                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1160
                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1161
                if(error_cause)
1162
                        g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
1163
                return error_code;
1164
        }
1165
        if(check_secret) {
1166
                char error_cause2[100];
1167
                JANUS_CHECK_SECRET((*videoroom)->room_secret, root, "secret", error_code, error_cause2,
1168
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1169
                if(error_code != 0) {
1170
                        g_strlcpy(error_cause, error_cause2, error_cause_size);
1171
                        return error_code;
1172
                }
1173
        }
1174
        if(check_pin) {
1175
                char error_cause2[100];
1176
                JANUS_CHECK_SECRET((*videoroom)->room_pin, root, "pin", error_code, error_cause2,
1177
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1178
                if(error_code != 0) {
1179
                        g_strlcpy(error_cause, error_cause2, error_cause_size);
1180
                        return error_code;
1181
                }
1182
        }
1183
        return 0;
1184
}
1185

    
1186
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
1187
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1188
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
1189
        
1190
        /* Pre-parse the message */
1191
        int error_code = 0;
1192
        char error_cause[512];
1193
        json_t *root = message;
1194
        json_t *response = NULL;
1195

    
1196
        if(message == NULL) {
1197
                JANUS_LOG(LOG_ERR, "No message??\n");
1198
                error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
1199
                g_snprintf(error_cause, 512, "%s", "No message??");
1200
                goto plugin_response;
1201
        }
1202

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

    
1649
                guint64 room_id = json_integer_value(room);
1650
                guint64 publisher_id = json_integer_value(pub_id);
1651
                guint32 stream_id = json_integer_value(id);
1652
                janus_mutex_lock(&rooms_mutex);
1653
                janus_videoroom *videoroom = NULL;
1654
                error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
1655
                janus_mutex_unlock(&rooms_mutex);
1656
                if(error_code != 0)
1657
                        goto plugin_response;
1658
                janus_mutex_lock(&videoroom->participants_mutex);
1659
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &publisher_id);
1660
                if(publisher == NULL) {
1661
                        janus_mutex_unlock(&videoroom->participants_mutex);
1662
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1663
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1664
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1665
                        goto plugin_response;
1666
                }
1667
                janus_mutex_lock(&publisher->rtp_forwarders_mutex);
1668
                if(g_hash_table_lookup(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id)) == NULL) {
1669
                        janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1670
                        janus_mutex_unlock(&videoroom->participants_mutex);
1671
                        JANUS_LOG(LOG_ERR, "No such stream (%"SCNu32")\n", stream_id);
1672
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1673
                        g_snprintf(error_cause, 512, "No such stream (%"SCNu32")", stream_id);
1674
                        goto plugin_response;
1675
                }
1676
                g_hash_table_remove(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id));
1677
                janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1678
                janus_mutex_unlock(&videoroom->participants_mutex);
1679
                response = json_object();
1680
                json_object_set_new(response, "videoroom", json_string("stop_rtp_forward"));
1681
                json_object_set_new(response, "room", json_integer(room_id));
1682
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1683
                json_object_set_new(response, "stream_id", json_integer(stream_id));
1684
                goto plugin_response;
1685
        } else if(!strcasecmp(request_text, "exists")) {
1686
                /* Check whether a given room exists or not, returns true/false */        
1687
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1688
                        error_code, error_cause, TRUE,
1689
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1690
                if(error_code != 0)
1691
                        goto plugin_response;
1692
                json_t *room = json_object_get(root, "room");
1693
                guint64 room_id = json_integer_value(room);
1694
                janus_mutex_lock(&rooms_mutex);
1695
                gboolean room_exists = g_hash_table_contains(rooms, &room_id);
1696
                janus_mutex_unlock(&rooms_mutex);
1697
                response = json_object();
1698
                json_object_set_new(response, "videoroom", json_string("success"));
1699
                json_object_set_new(response, "room", json_integer(room_id));
1700
                json_object_set_new(response, "exists", room_exists ? json_true() : json_false());
1701
                goto plugin_response;
1702
        } else if(!strcasecmp(request_text, "listparticipants")) {
1703
                /* List all participants in a room, specifying whether they're publishers or just attendees */        
1704
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1705
                        error_code, error_cause, TRUE,
1706
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1707
                if(error_code != 0)
1708
                        goto plugin_response;
1709
                json_t *room = json_object_get(root, "room");
1710
                guint64 room_id = json_integer_value(room);
1711
                janus_mutex_lock(&rooms_mutex);
1712
                janus_videoroom *videoroom = NULL;
1713
                error_code = janus_videoroom_access_room(root, FALSE, FALSE, &videoroom, error_cause, sizeof(error_cause));
1714
                janus_mutex_unlock(&rooms_mutex);
1715
                if(error_code != 0)
1716
                        goto plugin_response;
1717
                /* Return a list of all participants (whether they're publishing or not) */
1718
                json_t *list = json_array();
1719
                GHashTableIter iter;
1720
                gpointer value;
1721
                janus_mutex_lock(&videoroom->participants_mutex);
1722
                g_hash_table_iter_init(&iter, videoroom->participants);
1723
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1724
                        janus_videoroom_participant *p = value;
1725
                        json_t *pl = json_object();
1726
                        json_object_set_new(pl, "id", json_integer(p->user_id));
1727
                        if(p->display)
1728
                                json_object_set_new(pl, "display", json_string(p->display));
1729
                        json_object_set_new(pl, "publisher", (p->sdp && p->session->started) ? json_true() : json_false());
1730
                        json_array_append_new(list, pl);
1731
                }
1732
                janus_mutex_unlock(&videoroom->participants_mutex);
1733
                response = json_object();
1734
                json_object_set_new(response, "videoroom", json_string("participants"));
1735
                json_object_set_new(response, "room", json_integer(room_id));
1736
                json_object_set_new(response, "participants", list);
1737
                goto plugin_response;
1738
        } else if(!strcasecmp(request_text, "listforwarders")) {
1739
                /* List all forwarders in a room */        
1740
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1741
                        error_code, error_cause, TRUE,
1742
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
1743
                if(error_code != 0)
1744
                        goto plugin_response;
1745
                json_t *room = json_object_get(root, "room");
1746
                guint64 room_id = json_integer_value(room);
1747
                janus_mutex_lock(&rooms_mutex);
1748
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
1749
                if(videoroom == NULL) {
1750
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1751
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1752
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1753
                        janus_mutex_unlock(&rooms_mutex);
1754
                        goto plugin_response;
1755
                }
1756
                if(videoroom->destroyed) {
1757
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1758
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1759
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1760
                        janus_mutex_unlock(&rooms_mutex);
1761
                        goto plugin_response;
1762
                }
1763
                /* A secret may be required for this action */
1764
                JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
1765
                        JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
1766
                if(error_code != 0) {
1767
                        janus_mutex_unlock(&rooms_mutex);
1768
                        goto plugin_response;
1769
                }
1770
                /* Return a list of all forwarders */
1771
                json_t *list = json_array();
1772
                GHashTableIter iter;
1773
                gpointer value;
1774
                janus_mutex_lock(&videoroom->participants_mutex);
1775
                g_hash_table_iter_init(&iter, videoroom->participants);
1776
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1777
                        janus_videoroom_participant *p = value;
1778
                        if(g_hash_table_size(p->rtp_forwarders) == 0)
1779
                                continue;
1780
                        json_t *pl = json_object();
1781
                        json_object_set_new(pl, "publisher_id", json_integer(p->user_id));
1782
                        if(p->display)
1783
                                json_object_set_new(pl, "display", json_string(p->display));
1784
                        json_t *flist = json_array();
1785
                        GHashTableIter iter_f;
1786
                        gpointer key_f, value_f;                        
1787
                        g_hash_table_iter_init(&iter_f, p->rtp_forwarders);
1788
                        janus_mutex_lock(&p->rtp_forwarders_mutex);
1789
                        while(g_hash_table_iter_next(&iter_f, &key_f, &value_f)) {                                
1790
                                json_t *fl = json_object();
1791
                                guint32 rpk = GPOINTER_TO_UINT(key_f);
1792
                                rtp_forwarder *rpv = value_f;
1793
                                json_object_set_new(fl, "ip", json_string(inet_ntoa(rpv->serv_addr.sin_addr)));
1794
                                if(rpv->is_video > 0) {
1795
                                        json_object_set_new(fl, "video_stream_id", json_integer(rpk));
1796
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
1797
                                } else {
1798
                                        json_object_set_new(fl, "audio_stream_id", json_integer(rpk));
1799
                                        json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
1800
                                }
1801
                                json_array_append_new(flist, fl);
1802
                        }                
1803
                        janus_mutex_unlock(&p->rtp_forwarders_mutex);
1804
                        json_object_set_new(pl, "rtp_forwarder", flist);
1805
                        json_array_append_new(list, pl);
1806
                }
1807
                janus_mutex_unlock(&videoroom->participants_mutex);
1808
                janus_mutex_unlock(&rooms_mutex);
1809
                response = json_object();
1810
                json_object_set_new(response, "room", json_integer(room_id));
1811
                json_object_set_new(response, "rtp_forwarders", list);
1812
                goto plugin_response;
1813
        } else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")
1814
                        || !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish") || !strcasecmp(request_text, "unpublish")
1815
                        || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "switch") || !strcasecmp(request_text, "stop")
1816
                        || !strcasecmp(request_text, "add") || !strcasecmp(request_text, "remove") || !strcasecmp(request_text, "leave")) {
1817
                /* These messages are handled asynchronously */
1818

    
1819
                janus_videoroom_message *msg = g_malloc0(sizeof(janus_videoroom_message));
1820
                msg->handle = handle;
1821
                msg->transaction = transaction;
1822
                msg->message = root;
1823
                msg->jsep = jsep;
1824
                g_async_queue_push(messages, msg);
1825

    
1826
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
1827
        } else {
1828
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1829
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
1830
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1831
        }
1832

    
1833
plugin_response:
1834
                {
1835
                        if(error_code == 0 && !response) {
1836
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1837
                                g_snprintf(error_cause, 512, "Invalid response");
1838
                        }
1839
                        if(error_code != 0) {
1840
                                /* Prepare JSON error event */
1841
                                json_t *event = json_object();
1842
                                json_object_set_new(event, "videoroom", json_string("event"));
1843
                                json_object_set_new(event, "error_code", json_integer(error_code));
1844
                                json_object_set_new(event, "error", json_string(error_cause));
1845
                                response = event;
1846
                        }
1847
                        if(root != NULL)
1848
                                json_decref(root);
1849
                        if(jsep != NULL)
1850
                                json_decref(jsep);
1851
                        g_free(transaction);
1852

    
1853
                        return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
1854
                }
1855

    
1856
}
1857

    
1858
void janus_videoroom_setup_media(janus_plugin_session *handle) {
1859
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
1860
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1861
                return;
1862
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1863
        if(!session) {
1864
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1865
                return;
1866
        }
1867
        if(session->destroyed)
1868
                return;
1869
        g_atomic_int_set(&session->hangingup, 0);
1870

    
1871
        /* Media relaying can start now */
1872
        session->started = TRUE;
1873

    
1874
        if(session->participant) {
1875
                /* If this is a publisher, notify all listeners about the fact they can
1876
                 * now subscribe; if this is a listener, instead, ask the publisher a FIR */
1877
                if(session->participant_type == janus_videoroom_p_type_publisher) {
1878
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1879
                        /* Notify all other participants that there's a new boy in town */
1880
                        json_t *list = json_array();
1881
                        json_t *pl = json_object();
1882
                        json_object_set_new(pl, "id", json_integer(participant->user_id));
1883
                        if(participant->display)
1884
                                json_object_set_new(pl, "display", json_string(participant->display));
1885
                        json_array_append_new(list, pl);
1886
                        json_t *pub = json_object();
1887
                        json_object_set_new(pub, "videoroom", json_string("event"));
1888
                        json_object_set_new(pub, "room", json_integer(participant->room->room_id));
1889
                        json_object_set_new(pub, "publishers", list);
1890
                        GHashTableIter iter;
1891
                        gpointer value;
1892
                        janus_videoroom *videoroom = participant->room;
1893
                        janus_mutex_lock(&videoroom->participants_mutex);
1894
                        g_hash_table_iter_init(&iter, videoroom->participants);
1895
                        while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1896
                                janus_videoroom_participant *p = value;
1897
                                if(p == participant) {
1898
                                        continue;        /* Skip the new publisher itself */
1899
                                }
1900
                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1901
                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub, NULL);
1902
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1903
                        }
1904
                        json_decref(pub);
1905
                        janus_mutex_unlock(&videoroom->participants_mutex);
1906
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1907
                        janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
1908
                        if(l && l->feed) {
1909
                                janus_videoroom_participant *p = l->feed;
1910
                                if(p && p->session) {
1911
                                        /* Send a FIR */
1912
                                        char buf[20];
1913
                                        memset(buf, 0, 20);
1914
                                        janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
1915
                                        JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1916
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 20);
1917
                                        /* Send a PLI too, just in case... */
1918
                                        memset(buf, 0, 12);
1919
                                        janus_rtcp_pli((char *)&buf, 12);
1920
                                        JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1921
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 12);
1922
                                }
1923
                        }
1924
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1925
                        /* Do the same, but for all feeds */
1926
                        janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
1927
                        if(listener == NULL)
1928
                                return;
1929
                        GSList *ps = listener->listeners;
1930
                        while(ps) {
1931
                                janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
1932
                                if(l && l->feed) {
1933
                                        janus_videoroom_participant *p = l->feed;
1934
                                        if(p && p->session) {
1935
                                                /* Send a FIR */
1936
                                                char buf[20];
1937
                                                memset(buf, 0, 20);
1938
                                                janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
1939
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1940
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
1941
                                                /* Send a PLI too, just in case... */
1942
                                                memset(buf, 0, 12);
1943
                                                janus_rtcp_pli((char *)&buf, 12);
1944
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1945
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 12);
1946
                                        }
1947
                                }
1948
                                ps = ps->next;
1949
                        }
1950
                }
1951
        }
1952
}
1953

    
1954
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
1955
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
1956
                return;
1957
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1958
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
1959
                return;
1960
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1961
        if((!video && participant->audio_active) || (video && participant->video_active)) {
1962
                /* Update payload type and SSRC */
1963
                rtp_header *rtp = (rtp_header *)buf;
1964
                rtp->type = video ? participant->video_pt : participant->audio_pt;
1965
                rtp->ssrc = htonl(video ? participant->video_ssrc : participant->audio_ssrc);
1966
                /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
1967
                GHashTableIter iter;
1968
                gpointer value;
1969
                g_hash_table_iter_init(&iter, participant->rtp_forwarders);
1970
                janus_mutex_lock(&participant->rtp_forwarders_mutex);
1971
                while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
1972
                        rtp_forwarder* rtp_forward = (rtp_forwarder*)value;
1973
                        if(video && rtp_forward->is_video) {
1974
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
1975
                        }
1976
                        else if(!video && !rtp_forward->is_video) {
1977
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
1978
                        }
1979
                }
1980
                janus_mutex_unlock(&participant->rtp_forwarders_mutex);
1981
                /* Save the frame if we're recording */
1982
                janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len);
1983
                /* Done, relay it */
1984
                janus_videoroom_rtp_relay_packet packet;
1985
                packet.data = rtp;
1986
                packet.length = len;
1987
                packet.is_video = video;
1988
                /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */
1989
                packet.timestamp = ntohl(packet.data->timestamp);
1990
                packet.seq_number = ntohs(packet.data->seq_number);
1991
                /* Go */
1992
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
1993
                
1994
                /* Check if we need to send any REMB, FIR or PLI back to this publisher */
1995
                if(video && participant->video_active) {
1996
                        /* Did we send a REMB already, or is it time to send one? */
1997
                        gboolean send_remb = FALSE;
1998
                        if(participant->remb_latest == 0 && participant->remb_startup > 0) {
1999
                                /* Still in the starting phase, send the ramp-up REMB feedback */
2000
                                send_remb = TRUE;
2001
                        } else if(participant->remb_latest > 0 && janus_get_monotonic_time()-participant->remb_latest >= 5*G_USEC_PER_SEC) {
2002
                                /* 5 seconds have passed since the last REMB, send a new one */
2003
                                send_remb = TRUE;
2004
                        }                
2005
                        if(send_remb) {
2006
                                /* We send a few incremental REMB messages at startup */
2007
                                uint64_t bitrate = (participant->bitrate ? participant->bitrate : 256*1024);
2008
                                if(participant->remb_startup > 0) {
2009
                                        bitrate = bitrate/participant->remb_startup;
2010
                                        participant->remb_startup--;
2011
                                }
2012
                                JANUS_LOG(LOG_VERB, "Sending REMB (%s, %"SCNu64")\n", participant->display, bitrate);
2013
                                char rtcpbuf[24];
2014
                                janus_rtcp_remb((char *)(&rtcpbuf), 24, bitrate);
2015
                                gateway->relay_rtcp(handle, video, rtcpbuf, 24);
2016
                                if(participant->remb_startup == 0)
2017
                                        participant->remb_latest = janus_get_monotonic_time();
2018
                        }
2019
                        /* Generate FIR/PLI too, if needed */
2020
                        if(video && participant->video_active && (participant->room->fir_freq > 0)) {
2021
                                /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
2022
                                gint64 now = janus_get_monotonic_time();
2023
                                if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
2024
                                        /* FIXME We send a FIR every tot seconds */
2025
                                        participant->fir_latest = now;
2026
                                        char rtcpbuf[24];
2027
                                        memset(rtcpbuf, 0, 24);
2028
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &participant->fir_seq);
2029
                                        JANUS_LOG(LOG_VERB, "Sending FIR to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
2030
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 20);
2031
                                        /* Send a PLI too, just in case... */
2032
                                        memset(rtcpbuf, 0, 12);
2033
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2034
                                        JANUS_LOG(LOG_VERB, "Sending PLI to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
2035
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 12);
2036
                                }
2037
                        }
2038
                }
2039
        }
2040
}
2041

    
2042
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
2043
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2044
                return;
2045
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2046
        if(!session) {
2047
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2048
                return;
2049
        }
2050
        if(session->destroyed)
2051
                return;
2052
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
2053
                /* A listener sent some RTCP, check what it is and if we need to forward it to the publisher */
2054
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
2055
                if(!l->video)
2056
                        return;        /* The only feedback we handle is video related anyway... */
2057
                if(janus_rtcp_has_fir(buf, len)) {
2058
                        /* We got a FIR, forward it to the publisher */
2059
                        if(l && l->feed) {
2060
                                janus_videoroom_participant *p = l->feed;
2061
                                if(p && p->session) {
2062
                                        char rtcpbuf[20];
2063
                                        memset(rtcpbuf, 0, 20);
2064
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &p->fir_seq);
2065
                                        JANUS_LOG(LOG_VERB, "Got a FIR from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2066
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 20);
2067
                                }
2068
                        }
2069
                }
2070
                if(janus_rtcp_has_pli(buf, len)) {
2071
                        /* We got a PLI, forward it to the publisher */
2072
                        if(l && l->feed) {
2073
                                janus_videoroom_participant *p = l->feed;
2074
                                if(p && p->session) {
2075
                                        char rtcpbuf[12];
2076
                                        memset(rtcpbuf, 0, 12);
2077
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
2078
                                        JANUS_LOG(LOG_VERB, "Got a PLI from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2079
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 12);
2080
                                }
2081
                        }
2082
                }
2083
                uint64_t bitrate = janus_rtcp_get_remb(buf, len);
2084
                if(bitrate > 0) {
2085
                        /* FIXME We got a REMB from this listener, should we do something about it? */
2086
                }
2087
        }
2088
}
2089

    
2090
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
2091
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2092
                return;
2093
        if(buf == NULL || len <= 0)
2094
                return;
2095
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2096
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
2097
                return;
2098
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2099
        /* Get a string out of the data */
2100
        char *text = g_malloc0(len+1);
2101
        memcpy(text, buf, len);
2102
        *(text+len) = '\0';
2103
        JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
2104
        g_slist_foreach(participant->listeners, janus_videoroom_relay_data_packet, text);
2105
        g_free(text);
2106
}
2107

    
2108
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
2109
        /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */
2110
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
2111
                return;
2112
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
2113
        if(!session || session->destroyed || !session->participant)
2114
                return;
2115
        /* Check if it's an uplink (publisher) or downlink (viewer) issue */
2116
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2117
                if(!uplink) {
2118
                        janus_videoroom_participant *publisher = (janus_videoroom_participant *)session->participant;
2119
                        if(publisher) {
2120
                                /* Send an event on the handle to notify the application: it's
2121
                                 * up to the application to then choose a policy and enforce it */
2122
                                json_t *event = json_object();
2123
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2124
                                /* Also add info on what the current bitrate cap is */
2125
                                uint64_t bitrate = (publisher->bitrate ? publisher->bitrate : 256*1024);
2126
                                json_object_set_new(event, "current-bitrate", json_integer(bitrate));
2127
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
2128
                                json_decref(event);
2129
                        }
2130
                } else {
2131
                        JANUS_LOG(LOG_WARN, "Got a slow uplink on a VideoRoom publisher? Weird, because it doesn't receive media...\n");
2132
                }
2133
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2134
                if(uplink) {
2135
                        janus_videoroom_listener *viewer = (janus_videoroom_listener *)session->participant;
2136
                        if(viewer) {
2137
                                /* Send an event on the handle to notify the application: it's
2138
                                 * up to the application to then choose a policy and enforce it */
2139
                                json_t *event = json_object();
2140
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
2141
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
2142
                                json_decref(event);
2143
                        }
2144
                } else {
2145
                        JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
2146
                }
2147
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2148
                /* TBD. */
2149
        }
2150
}
2151

    
2152
static void janus_videoroom_recorder_create(janus_videoroom_participant *participant, gboolean audio, gboolean video) {
2153
        char filename[255];
2154
        gint64 now = janus_get_real_time();
2155
        if(audio) {
2156
                memset(filename, 0, 255);
2157
                if(participant->recording_base) {
2158
                        /* Use the filename and path we have been provided */
2159
                        g_snprintf(filename, 255, "%s-audio", participant->recording_base);
2160
                        participant->arc = janus_recorder_create(participant->room->rec_dir,
2161
                                janus_videoroom_audiocodec_name(participant->room->acodec), filename);
2162
                        if(participant->arc == NULL) {
2163
                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2164
                        }
2165
                } else {
2166
                        /* Build a filename */
2167
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
2168
                                participant->room->room_id, participant->user_id, now);
2169
                        participant->arc = janus_recorder_create(participant->room->rec_dir,
2170
                                janus_videoroom_audiocodec_name(participant->room->acodec), filename);
2171
                        if(participant->arc == NULL) {
2172
                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2173
                        }
2174
                }
2175
        }
2176
        if(video) {
2177
                memset(filename, 0, 255);
2178
                if(participant->recording_base) {
2179
                        /* Use the filename and path we have been provided */
2180
                        g_snprintf(filename, 255, "%s-video", participant->recording_base);
2181
                        participant->vrc = janus_recorder_create(participant->room->rec_dir,
2182
                                janus_videoroom_videocodec_name(participant->room->vcodec), filename);
2183
                        if(participant->vrc == NULL) {
2184
                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2185
                        }
2186
                } else {
2187
                        /* Build a filename */
2188
                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
2189
                                participant->room->room_id, participant->user_id, now);
2190
                        participant->vrc = janus_recorder_create(participant->room->rec_dir,
2191
                                janus_videoroom_videocodec_name(participant->room->vcodec), filename);
2192
                        if(participant->vrc == NULL) {
2193
                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2194
                        }
2195
                }
2196
        }
2197
}
2198

    
2199
static void janus_videoroom_recorder_close(janus_videoroom_participant *participant) {
2200
        if(participant->arc) {
2201
                janus_recorder_close(participant->arc);
2202
                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
2203
                janus_recorder_free(participant->arc);
2204
        }
2205
        participant->arc = NULL;
2206
        if(participant->vrc) {
2207
                janus_recorder_close(participant->vrc);
2208
                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
2209
                janus_recorder_free(participant->vrc);
2210
        }
2211
        participant->vrc = NULL;
2212
}
2213

    
2214
void janus_videoroom_hangup_media(janus_plugin_session *handle) {
2215
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
2216
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2217
                return;
2218
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
2219
        if(!session) {
2220
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2221
                return;
2222
        }
2223
        session->started = FALSE;
2224
        if(session->destroyed)
2225
                return;
2226
        if(g_atomic_int_add(&session->hangingup, 1))
2227
                return;
2228
        /* Send an event to the browser and tell the PeerConnection is over */
2229
        if(session->participant_type == janus_videoroom_p_type_publisher) {
2230
                /* This publisher just 'unpublished' */
2231
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2232
                if(participant->sdp)
2233
                        g_free(participant->sdp);
2234
                participant->sdp = NULL;
2235
                participant->firefox = FALSE;
2236
                participant->audio_active = FALSE;
2237
                participant->video_active = FALSE;
2238
                participant->remb_startup = 4;
2239
                participant->remb_latest = 0;
2240
                participant->fir_latest = 0;
2241
                participant->fir_seq = 0;
2242
                /* Get rid of the recorders, if available */
2243
                janus_mutex_lock(&participant->rec_mutex);
2244
                janus_videoroom_recorder_close(participant);
2245
                janus_mutex_unlock(&participant->rec_mutex);
2246
                janus_mutex_lock(&participant->listeners_mutex);
2247
                while(participant->listeners) {
2248
                        janus_videoroom_listener *l = (janus_videoroom_listener *)participant->listeners->data;
2249
                        if(l) {
2250
                                participant->listeners = g_slist_remove(participant->listeners, l);
2251
                                l->feed = NULL;
2252
                        }
2253
                }
2254
                janus_mutex_unlock(&participant->listeners_mutex);
2255
                janus_videoroom_leave_or_unpublish(participant, FALSE);
2256
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2257
                /* Get rid of listener */
2258
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2259
                if(listener) {
2260
                        listener->paused = TRUE;
2261
                        janus_videoroom_participant *publisher = listener->feed;
2262
                        if(publisher != NULL) {
2263
                                janus_mutex_lock(&publisher->listeners_mutex);
2264
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
2265
                                janus_mutex_unlock(&publisher->listeners_mutex);
2266
                                listener->feed = NULL;
2267
                        }
2268
                }
2269
                /* TODO Should we close the handle as well? */
2270
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2271
                /* Do the same, but for all sub-listener */
2272
                janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
2273
                GSList *ps = listener->listeners;
2274
                while(ps) {
2275
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
2276
                        if(l) {
2277
                                l->paused = TRUE;
2278
                                janus_videoroom_participant *publisher = l->feed;
2279
                                if(publisher != NULL) {
2280
                                        janus_mutex_lock(&publisher->listeners_mutex);
2281
                                        publisher->listeners = g_slist_remove(publisher->listeners, l);
2282
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2283
                                        l->feed = NULL;
2284
                                }
2285
                        }
2286
                        /* TODO Should we close the handle as well? */
2287
                        ps = ps->next;
2288
                }
2289
                /* TODO Should we close the handle as well? */
2290
        }
2291
}
2292

    
2293
static void janus_videoroom_sdp_a_format(char *mline, int mline_size, janus_videoroom_audiocodec acodec, int pt, const char *audio_mode) {
2294
        switch(acodec) {
2295
                case JANUS_VIDEOROOM_OPUS:
2296
                        g_snprintf(mline, mline_size, sdp_a_template_opus,
2297
                                pt,                                                /* Opus payload type */
2298
                                audio_mode,
2299
                                pt);                                                 /* Opus payload type */
2300
                        break;
2301
                case JANUS_VIDEOROOM_ISAC_32K:
2302
                        g_snprintf(mline, mline_size, sdp_a_template_isac32,
2303
                                pt,                                                /* ISAC 32K payload type */
2304
                                audio_mode,
2305
                                pt);                                         /* ISAC 32K payload type */
2306
                        break;
2307
                case JANUS_VIDEOROOM_ISAC_16K:
2308
                        g_snprintf(mline, mline_size, sdp_a_template_isac16,
2309
                                pt,                                                /* ISAC 16K payload type */
2310
                                audio_mode,
2311
                                pt);                                                /* ISAC 16K payload type */
2312
                        break;
2313
                case JANUS_VIDEOROOM_PCMU:
2314
                        g_snprintf(mline, mline_size, sdp_a_template_pcmu,
2315
                                pt,                                                /* PCMU payload type */
2316
                                audio_mode,
2317
                                pt);                                                /* PCMU payload type */
2318
                        break;
2319
                case JANUS_VIDEOROOM_PCMA:
2320
                        g_snprintf(mline, mline_size, sdp_a_template_pcma,
2321
                                pt,                                                /* PCMA payload type */
2322
                                audio_mode,
2323
                                pt);                                                /* PCMA payload type */
2324
                        break;
2325
                default:
2326
                        /* Shouldn't happen */
2327
                        mline[0] = '\0';
2328
                        break;
2329
        }
2330
}
2331

    
2332
static void janus_videoroom_sdp_v_format(char *mline, int mline_size, janus_videoroom_videocodec vcodec, int pt, int b, const char *video_mode) {
2333
        switch(vcodec) {
2334
                case JANUS_VIDEOROOM_VP8:
2335
                        g_snprintf(mline, mline_size, sdp_v_template_vp8,
2336
                                pt,                                                        /* payload type */
2337
                                b,                                                                /* Bandwidth */
2338
                                video_mode,                                                /* The publisher gets a recvonly or inactive back */
2339
                                pt,                                                 /* payload type */
2340
                                pt,                                                 /* payload type */
2341
                                pt,                                                 /* payload type */
2342
                                pt,                                                 /* payload type */
2343
                                pt);                                                 /* payload type */
2344
                        break;
2345
                case JANUS_VIDEOROOM_VP9:
2346
                        g_snprintf(mline, mline_size, sdp_v_template_vp9,
2347
                                pt,                                                        /* payload type */
2348
                                b,                                                                /* Bandwidth */
2349
                                video_mode,                                                /* The publisher gets a recvonly or inactive back */
2350
                                pt,                                                 /* payload type */
2351
                                pt,                                                 /* payload type */
2352
                                pt,                                                 /* payload type */
2353
                                pt,                                                 /* payload type */
2354
                                pt);                                                 /* payload type */
2355
                        break;
2356
                case JANUS_VIDEOROOM_H264:
2357
                        g_snprintf(mline, mline_size, sdp_v_template_h264,
2358
                                pt,                                                        /* payload type */
2359
                                b,                                                                /* Bandwidth */
2360
                                video_mode,                                                /* The publisher gets a recvonly or inactive back */
2361
                                pt,                                                 /* payload type */
2362
                                pt,                                                 /* payload type */
2363
                                pt,                                                 /* payload type */
2364
                                pt,                                                 /* payload type */
2365
                                pt,                                                 /* payload type */
2366
                                pt);                                                 /* payload type */
2367
                        break;
2368
                default:
2369
                        /* Shouldn't happen */
2370
                        mline[0] = '\0';
2371
                        break;
2372
        }
2373
}
2374

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

    
3248
                /* Prepare JSON event */
3249
                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3250
                /* Any SDP to handle? */
3251
                const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
3252
                const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
3253
                if(!msg_sdp) {
3254
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3255
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3256
                        json_decref(event);
3257
                } else {
3258
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
3259
                        const char *type = NULL;
3260
                        if(!strcasecmp(msg_sdp_type, "offer")) {
3261
                                /* We need to answer */
3262
                                type = "answer";
3263
                        } else if(!strcasecmp(msg_sdp_type, "answer")) {
3264
                                /* We got an answer (from a listener?), no need to negotiate */
3265
                                g_atomic_int_set(&session->hangingup, 0);
3266
                                int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3267
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3268
                                json_decref(event);
3269
                                janus_videoroom_message_free(msg);
3270
                                continue;
3271
                        } else {
3272
                                /* TODO We don't support anything else right now... */
3273
                                JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg_sdp_type);
3274
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3275
                                g_snprintf(error_cause, 512, "Unknown SDP type '%s'", msg_sdp_type);
3276
                                goto error;
3277
                        }
3278
                        if(session->participant_type != janus_videoroom_p_type_publisher) {
3279
                                /* We shouldn't be here, we always offer ourselves */
3280
                                JANUS_LOG(LOG_ERR, "Only publishers send offers\n");
3281
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3282
                                g_snprintf(error_cause, 512, "Only publishers send offers");
3283
                                goto error;
3284
                        } else {
3285
                                /* This is a new publisher: is there room? */
3286
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
3287
                                janus_videoroom *videoroom = participant->room;
3288
                                int count = 0;
3289
                                GHashTableIter iter;
3290
                                gpointer value;
3291
                                if(!videoroom) {
3292
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3293
                                        goto error;
3294
                                }
3295
                                if(videoroom->destroyed) {
3296
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3297
                                        goto error;
3298
                                }
3299
                                janus_mutex_lock(&videoroom->participants_mutex);
3300
                                g_hash_table_iter_init(&iter, videoroom->participants);
3301
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3302
                                        janus_videoroom_participant *p = value;
3303
                                        if(p != participant && p->sdp)
3304
                                                count++;
3305
                                }
3306
                                janus_mutex_unlock(&videoroom->participants_mutex);
3307
                                if(count == videoroom->max_publishers) {
3308
                                        participant->audio_active = FALSE;
3309
                                        participant->video_active = FALSE;
3310
                                        JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
3311
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3312
                                        g_snprintf(error_cause, 512, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
3313
                                        goto error;
3314
                                }
3315
                                /* Now prepare the SDP to give back */
3316
                                if(strstr(msg_sdp, "Mozilla")) {
3317
                                        participant->firefox = TRUE;
3318
                                }
3319
                                /* Which media are available? */
3320
                                int audio = 0, video = 0, data = 0;
3321
                                const char *audio_mode = NULL, *video_mode = NULL;
3322
                                sdp_parser_t *parser = sdp_parse(sdphome, msg_sdp, strlen(msg_sdp), 0);
3323
                                sdp_session_t *parsed_sdp = sdp_session(parser);
3324
                                if(!parsed_sdp) {
3325
                                        /* Invalid SDP */
3326
                                        JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdp_parsing_error(parser));
3327
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3328
                                        g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdp_parsing_error(parser));
3329
                                        sdp_parser_free(parser);
3330
                                        goto error;
3331
                                }
3332
                                sdp_media_t *m = parsed_sdp->sdp_media;
3333
                                while(m) {
3334
                                        if(m->m_type == sdp_media_audio && m->m_port > 0) {
3335
                                                audio++;
3336
                                                participant->audio = TRUE;
3337
                                                if(audio > 1) {
3338
                                                        m = m->m_next;
3339
                                                        continue;
3340
                                                }
3341
                                        } else if(m->m_type == sdp_media_video && m->m_port > 0) {
3342
                                                video++;
3343
                                                participant->video = TRUE;
3344
                                                if(video > 1) {
3345
                                                        m = m->m_next;
3346
                                                        continue;
3347
                                                }
3348
                                        } else if(m->m_type == sdp_media_application && m->m_port > 0) {
3349
                                                data++;
3350
                                                participant->data = TRUE;
3351
                                                if(data > 1) {
3352
                                                        m = m->m_next;
3353
                                                        continue;
3354
                                                }
3355
                                        }
3356
                                        if(m->m_type != sdp_media_application) {
3357
                                                /* What is the direction? */
3358
                                                switch(m->m_mode) {
3359
                                                        case sdp_recvonly:
3360
                                                                /* If we're getting a 'recvonly' publisher, we're going to answer with 'inactive' */
3361
                                                        case sdp_inactive:
3362
                                                                if(m->m_type == sdp_media_audio) {
3363
                                                                        audio_mode = "inactive";
3364
                                                                } else {
3365
                                                                        video_mode = "inactive";
3366
                                                                }
3367
                                                                break;
3368
                                                        case sdp_sendonly:
3369
                                                                /* What we expect, turn this into 'recvonly' */
3370
                                                        case sdp_sendrecv:
3371
                                                        default:
3372
                                                                if(m->m_type == sdp_media_audio) {
3373
                                                                        audio_mode = "recvonly";
3374
                                                                } else {
3375
                                                                        video_mode = "recvonly";
3376
                                                                }
3377
                                                                break;
3378
                                                }
3379
                                        }
3380
                                        m = m->m_next;
3381
                                }
3382
                                sdp_parser_free(parser);
3383
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", audio ? "is" : "is NOT");
3384
                                int opus_pt = 0, isac32_pt = 0, isac16_pt = 0, pcmu_pt = 0, pcma_pt = 0,
3385
                                        vp8_pt = 0, vp9_pt = 0, h264_pt = 0;
3386
                                if(audio) {
3387
                                        JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", audio_mode);
3388
                                        opus_pt = janus_get_codec_pt(msg_sdp, "opus");
3389
                                        if(opus_pt > 0) {
3390
                                                JANUS_LOG(LOG_VERB, "  -- -- Opus payload type is %d\n", opus_pt);
3391
                                        }
3392
                                        isac32_pt = janus_get_codec_pt(msg_sdp, "isac32");
3393
                                        if(isac32_pt > 0) {
3394
                                                JANUS_LOG(LOG_VERB, "  -- -- ISAC 32K payload type is %d\n", isac32_pt);
3395
                                        }
3396
                                        isac16_pt = janus_get_codec_pt(msg_sdp, "isac16");
3397
                                        if(isac16_pt > 0) {
3398
                                                JANUS_LOG(LOG_VERB, "  -- -- ISAC 16K payload type is %d\n", isac16_pt);
3399
                                        }
3400
                                        pcmu_pt = janus_get_codec_pt(msg_sdp, "pcmu");
3401
                                        if(pcmu_pt > 0) {
3402
                                                JANUS_LOG(LOG_VERB, "  -- -- PCMU payload type is %d\n", pcmu_pt);
3403
                                        }
3404
                                        pcma_pt = janus_get_codec_pt(msg_sdp, "pcma");
3405
                                        if(pcma_pt > 0) {
3406
                                                JANUS_LOG(LOG_VERB, "  -- -- PCMA payload type is %d\n", pcma_pt);
3407
                                        }
3408
                                }
3409
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", video ? "is" : "is NOT");
3410
                                if(video) {
3411
                                        JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", video_mode);
3412
                                        vp8_pt = janus_get_codec_pt(msg_sdp, "vp8");
3413
                                        if(vp8_pt > 0) {
3414
                                                JANUS_LOG(LOG_VERB, "  -- -- VP8 payload type is %d\n", vp8_pt);
3415
                                        }
3416
                                        vp9_pt = janus_get_codec_pt(msg_sdp, "vp9");
3417
                                        if(vp9_pt > 0) {
3418
                                                JANUS_LOG(LOG_VERB, "  -- -- VP9 payload type is %d\n", vp9_pt);
3419
                                        }
3420
                                        h264_pt = janus_get_codec_pt(msg_sdp, "h264");
3421
                                        if(h264_pt > 0) {
3422
                                                JANUS_LOG(LOG_VERB, "  -- -- H264 payload type is %d\n", h264_pt);
3423
                                        }
3424
                                }
3425
                                JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", data ? "is" : "is NOT");
3426
                                /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
3427
                                int b = 0;
3428
                                if(participant->firefox)        /* Don't add any b=AS attribute for Chrome */
3429
                                        b = (int)(videoroom->bitrate/1000);
3430
                                char sdp[1280], audio_mline[256], video_mline[512], data_mline[256];
3431
                                char *newsdp = NULL;
3432
                                int res = 0;
3433
                                int pass = 0;
3434
                                for(pass = 1; pass <= 2; pass++) {
3435
                                        if(pass == 2) {
3436
                                                /* Now turn the SDP into what we'll send subscribers, using the static payload types for making switching easier */
3437
                                                if(audio_mode && strcmp(audio_mode, "inactive"))
3438
                                                        /* The publisher gets a recvonly or inactive back */
3439
                                                        /* Subscribers gets a sendonly or inactive back */
3440
                                                        audio_mode = "sendonly";
3441
                                                if(video_mode && strcmp(video_mode, "inactive"))
3442
                                                        video_mode = "sendonly";
3443
                                        }
3444
                                        audio_mline[0] = '\0';
3445
                                        if(audio) {
3446
                                                int pt = -1;
3447
                                                switch(videoroom->acodec) {
3448
                                                        case JANUS_VIDEOROOM_OPUS:
3449
                                                                if(opus_pt >= 0)
3450
                                                                        pt = (pass == 1 ? opus_pt : OPUS_PT);
3451
                                                                break;
3452
                                                        case JANUS_VIDEOROOM_ISAC_32K:
3453
                                                                if(isac32_pt >= 0)
3454
                                                                        pt = (pass == 1 ? isac32_pt : ISAC32_PT);
3455
                                                                break;
3456
                                                        case JANUS_VIDEOROOM_ISAC_16K:
3457
                                                                if(isac16_pt >= 0)
3458
                                                                        pt = (pass == 1 ? isac16_pt : ISAC16_PT);
3459
                                                                break;
3460
                                                        case JANUS_VIDEOROOM_PCMU:
3461
                                                                if(pcmu_pt >= 0)
3462
                                                                        pt = (pass == 1 ? pcmu_pt : PCMU_PT);
3463
                                                                break;
3464
                                                        case JANUS_VIDEOROOM_PCMA:
3465
                                                                if(pcma_pt >= 0)
3466
                                                                        pt = (pass == 1 ? pcma_pt : PCMA_PT);
3467
                                                                break;
3468
                                                        default:
3469
                                                                /* Shouldn't happen */
3470
                                                                break;
3471
                                                }
3472
                                                if(pass == 1 && pt < 0)
3473
                                                        JANUS_LOG(LOG_WARN, "Videoroom is forcing %s, but publisher didn't offer any... rejecting audio\n", janus_videoroom_audiocodec_name(videoroom->acodec));
3474
                                                if(pt >= 0)
3475
                                                        janus_videoroom_sdp_a_format(audio_mline, 256, videoroom->acodec, pt, audio_mode);
3476
                                                if(audio_mline[0] == '\0' && pass == 1) {
3477
                                                        /* Remove "pass == 1" if the listener also should get a line with port=0. */
3478
                                                        g_snprintf(audio_mline, 256, "m=audio 0 RTP/SAVPF 0\r\n");
3479
                                                }
3480
                                        }
3481
                                        video_mline[0] = '\0';
3482
                                        if(video) {
3483
                                                int pt = -1;
3484
                                                switch(videoroom->vcodec) {
3485
                                                        case JANUS_VIDEOROOM_VP8:
3486
                                                                if(vp8_pt >= 0)
3487
                                                                        pt = (pass == 1 ? vp8_pt : VP8_PT);
3488
                                                                break;
3489
                                                        case JANUS_VIDEOROOM_VP9:
3490
                                                                if(vp9_pt >= 0)
3491
                                                                        pt = (pass == 1 ? vp9_pt : VP9_PT);
3492
                                                                break;
3493
                                                        case JANUS_VIDEOROOM_H264:
3494
                                                                if(h264_pt >= 0)
3495
                                                                        pt = (pass == 1 ? h264_pt : H264_PT);
3496
                                                                break;
3497
                                                        default:
3498
                                                                /* Shouldn't happen */
3499
                                                                break;
3500
                                                }
3501
                                                if(pass == 1 && pt < 0)
3502
                                                        JANUS_LOG(LOG_WARN, "Videoroom is forcing %s, but publisher didn't offer any... rejecting video\n", janus_videoroom_videocodec_name(videoroom->vcodec));
3503
                                                if(pt >= 0)
3504
                                                        janus_videoroom_sdp_v_format(video_mline, 512, videoroom->vcodec, pt, b, video_mode);
3505
                                                if(video_mline[0] == '\0' && pass == 1) {
3506
                                                        /* Remove "pass == 1" if the listener also should get a line with port=0. */
3507
                                                        g_snprintf(video_mline, 512, "m=video 0 RTP/SAVPF 0\r\n");
3508
                                                }
3509
                                        }
3510
                                        if(data) {
3511
                                                g_snprintf(data_mline, 256, sdp_d_template);
3512
                                        } else {
3513
                                                data_mline[0] = '\0';
3514
                                        }
3515
                                        g_snprintf(sdp, 1280, sdp_template,
3516
                                                janus_get_real_time(),                        /* We need current time here */
3517
                                                janus_get_real_time(),                        /* We need current time here */
3518
                                                participant->room->room_name,        /* Video room name */
3519
                                                audio_mline,                                        /* Audio m-line, if any */
3520
                                                video_mline,                                        /* Video m-line, if any */
3521
                                                data_mline);                                        /* Data channel m-line, if any */
3522
                                        newsdp = g_strdup(sdp);
3523
                                        if(video && b == 0) {
3524
                                                /* Remove useless bandwidth attribute */
3525
                                                newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
3526
                                        }
3527
                                        if(pass == 2)
3528
                                                break;
3529
                                        /* Is this room recorded? */
3530
                                        janus_mutex_lock(&participant->rec_mutex);
3531
                                        if(videoroom->record || participant->recording_active) {
3532
                                                janus_videoroom_recorder_create(participant, audio, video);
3533
                                        }
3534
                                        janus_mutex_unlock(&participant->rec_mutex);
3535

    
3536
                                        JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, newsdp);
3537
                                        json_t *jsep = json_pack("{ssss}", "type", type, "sdp", 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
                                        res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
3542
                                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3543
                                        json_decref(event);
3544
                                        json_decref(jsep);
3545
                                        g_free(newsdp);
3546
                                }
3547
                                /* Done */
3548
                                if(res != JANUS_OK) {
3549
                                        /* TODO Failed to negotiate? We should remove this publisher */
3550
                                        g_free(newsdp);
3551
                                } else {
3552
                                        /* Store the participant's SDP for interested listeners */
3553
                                        participant->sdp = newsdp;
3554
                                        /* We'll wait for the setup_media event before actually telling listeners */
3555
                                }
3556
                        }
3557
                }
3558
                janus_videoroom_message_free(msg);
3559

    
3560
                continue;
3561
                
3562
error:
3563
                {
3564
                        /* Prepare JSON error event */
3565
                        json_t *event = json_object();
3566
                        json_object_set_new(event, "videoroom", json_string("event"));
3567
                        json_object_set_new(event, "error_code", json_integer(error_code));
3568
                        json_object_set_new(event, "error", json_string(error_cause));
3569
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
3570
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
3571
                        json_decref(event);
3572
                        janus_videoroom_message_free(msg);
3573
                }
3574
        }
3575
        JANUS_LOG(LOG_VERB, "Leaving VideoRoom handler thread\n");
3576
        return NULL;
3577
}
3578

    
3579

    
3580
/* Multiplexing helpers */
3581
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
3582
        if(!muxed_listener || !feeds)
3583
                return -1;
3584
        janus_mutex_lock(&muxed_listener->listeners_mutex);
3585
        JANUS_LOG(LOG_VERB, "Subscribing to %d feeds\n", g_list_length(feeds));
3586
        janus_videoroom *videoroom = muxed_listener->room;
3587
        GList *ps = feeds;
3588
        json_t *list = json_array();
3589
        int added_feeds = 0;
3590
        while(ps) {
3591
                uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
3592
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, &feed_id);
3593
                if(publisher == NULL) { //~ || publisher->sdp == NULL) {
3594
                        /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
3595
                        JANUS_LOG(LOG_WARN, "No such feed (%"SCNu64"), skipping\n", feed_id);
3596
                        ps = ps->next;
3597
                        continue;
3598
                }
3599
                /* Are we already subscribed? */
3600
                gboolean subscribed = FALSE;
3601
                GSList *ls = muxed_listener->listeners;
3602
                while(ls) {
3603
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ls->data;
3604
                        if(l && (l->feed == publisher)) {
3605
                                subscribed = TRUE;
3606
                                JANUS_LOG(LOG_WARN, "Already subscribed to feed %"SCNu64", skipping\n", feed_id);
3607
                                break;
3608
                        }
3609
                        ls = ls->next;
3610
                }
3611
                if(subscribed) {
3612
                        ps = ps->next;
3613
                        continue;
3614
                }
3615
                janus_videoroom_listener *listener = g_malloc0(sizeof(janus_videoroom_listener));
3616
                listener->session = muxed_listener->session;
3617
                listener->room = videoroom;
3618
                listener->feed = publisher;
3619
                //~ listener->paused = TRUE;        /* We need an explicit start from the listener */
3620
                listener->paused = FALSE;
3621
                listener->parent = muxed_listener;
3622
                janus_mutex_lock(&publisher->listeners_mutex);
3623
                publisher->listeners = g_slist_append(publisher->listeners, listener);
3624
                janus_mutex_unlock(&publisher->listeners_mutex);
3625
                muxed_listener->listeners = g_slist_append(muxed_listener->listeners, listener);
3626
                JANUS_LOG(LOG_VERB, "Now subscribed to %d feeds\n", g_slist_length(muxed_listener->listeners));
3627
                /* Add to feeds in the answer */
3628
                added_feeds++;
3629
                json_t *f = json_object();
3630
                json_object_set_new(f, "id", json_integer(feed_id));
3631
                if(publisher->display)
3632
                        json_object_set_new(f, "display", json_string(publisher->display));
3633
                json_array_append_new(list, f);
3634
                ps = ps->next;
3635
        }
3636
        janus_mutex_unlock(&muxed_listener->listeners_mutex);
3637
        if(added_feeds == 0) {
3638
                /* Nothing changed */
3639
                return 0;
3640
        }
3641
        /* Prepare event */
3642
        json_t *event = json_object();
3643
        json_object_set_new(event, "videoroom", json_string("muxed-attached"));
3644
        json_object_set_new(event, "room", json_integer(videoroom->room_id));
3645
        json_object_set_new(event, "feeds", list);
3646
        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3647
        /* Send the updated offer */
3648
        return janus_videoroom_muxed_offer(muxed_listener, transaction, event);
3649
}
3650

    
3651
int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
3652
        janus_mutex_lock(&muxed_listener->listeners_mutex);
3653
        JANUS_LOG(LOG_VERB, "Unsubscribing from %d feeds\n", g_list_length(feeds));
3654
        janus_videoroom *videoroom = muxed_listener->room;
3655
        GList *ps = feeds;
3656
        json_t *list = json_array();
3657
        int removed_feeds = 0;
3658
        while(ps) {
3659
                uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
3660
                GSList *ls = muxed_listener->listeners;
3661
                while(ls) {
3662
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)ls->data;
3663
                        if(listener) {
3664
                                janus_videoroom_participant *publisher = listener->feed;
3665
                                if(publisher == NULL || publisher->user_id != feed_id) {
3666
                                        /* Not the publisher we're looking for */
3667
                                        ls = ls->next;
3668
                                        continue;
3669
                                }
3670
                                janus_mutex_lock(&publisher->listeners_mutex);
3671
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
3672
                                janus_mutex_unlock(&publisher->listeners_mutex);
3673
                                listener->feed = NULL;
3674
                                muxed_listener->listeners = g_slist_remove(muxed_listener->listeners, listener);
3675
                                JANUS_LOG(LOG_VERB, "Now subscribed to %d feeds\n", g_slist_length(muxed_listener->listeners));
3676
                                janus_videoroom_listener_free(listener);
3677
                                /* Add to feeds in the answer */
3678
                                removed_feeds++;
3679
                                json_t *f = json_object();
3680
                                json_object_set_new(f, "id", json_integer(feed_id));
3681
                                json_array_append_new(list, f);
3682
                                break;
3683
                        }
3684
                        ls = ls->next;
3685
                }
3686
                ps = ps->next;
3687
        }
3688
        janus_mutex_unlock(&muxed_listener->listeners_mutex);
3689
        if(removed_feeds == 0) {
3690
                /* Nothing changed */
3691
                return 0;
3692
        }
3693
        /* Prepare event */
3694
        json_t *event = json_object();
3695
        json_object_set_new(event, "videoroom", json_string("muxed-detached"));
3696
        json_object_set_new(event, "room", json_integer(videoroom->room_id));
3697
        json_object_set_new(event, "feeds", list);
3698
        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3699
        /* Send the updated offer */
3700
        return janus_videoroom_muxed_offer(muxed_listener, transaction, event);
3701
}
3702

    
3703
int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, json_t *event) {
3704
        if(muxed_listener == NULL)
3705
                return -1;
3706
        /* Negotiate by placing a 'muxed' fake attribute for each publisher we subscribed to,
3707
         * that will translate to multiple SSRCs when merging the SDP */
3708
        int audio = 0, video = 0;
3709
        char audio_muxed[1024], video_muxed[1024], temp[255];
3710
        char sdp[2048], audio_mline[512], video_mline[512], data_mline[1];
3711
        data_mline[0] = '\0'; /* Multiplexed streams do not support data channels */
3712
        memset(audio_muxed, 0, 1024);
3713
        memset(video_muxed, 0, 1024);
3714
        memset(audio_mline, 0, 512);
3715
        memset(video_mline, 0, 512);
3716
        /* Prepare the m-lines (FIXME this will result in an audio line even for video-only rooms, but we don't care) */
3717
        int pt = -1;
3718
        switch(muxed_listener->room->acodec) {
3719
                case JANUS_VIDEOROOM_OPUS:
3720
                        pt = OPUS_PT;
3721
                        break;
3722
                case JANUS_VIDEOROOM_ISAC_32K:
3723
                        pt = ISAC32_PT;
3724
                        break;
3725
                case JANUS_VIDEOROOM_ISAC_16K:
3726
                        pt = ISAC16_PT;
3727
                        break;
3728
                case JANUS_VIDEOROOM_PCMU:
3729
                        pt = PCMU_PT;
3730
                        break;
3731
                case JANUS_VIDEOROOM_PCMA:
3732
                        pt = PCMA_PT;
3733
                        break;
3734
                default:
3735
                        /* Shouldn't happen */
3736
                        break;
3737
        }
3738
        janus_videoroom_sdp_a_format(audio_mline, 512, muxed_listener->room->acodec, pt, "sendonly");
3739
        pt = -1;
3740
        switch(muxed_listener->room->vcodec) {
3741
                case JANUS_VIDEOROOM_VP8:
3742
                        pt = VP8_PT;
3743
                        break;
3744
                case JANUS_VIDEOROOM_VP9:
3745
                        pt = VP9_PT;
3746
                        break;
3747
                case JANUS_VIDEOROOM_H264:
3748
                        pt = H264_PT;
3749
                        break;
3750
                default:
3751
                        /* Shouldn't happen */
3752
                        break;
3753
        }
3754
        janus_videoroom_sdp_v_format(video_mline, 512, muxed_listener->room->vcodec, pt, 0, "sendonly");
3755
        /* FIXME Add a fake user/SSRC just to avoid the "Failed to set max send bandwidth for video content" bug */
3756
        g_strlcat(audio_muxed, "a=planb:sfu0 1\r\n", 1024);
3757
        g_strlcat(video_muxed, "a=planb:sfu0 2\r\n", 1024);
3758
        /* Go through all the available publishers */
3759
        GSList *ps = muxed_listener->listeners;
3760
        while(ps) {
3761
                janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
3762
                if(l && l->feed) { //~ && l->feed->sdp) {
3763
                        if(strstr(l->feed->sdp, "m=audio")) {
3764
                                audio++;
3765
                                g_snprintf(temp, 255, "a=planb:sfu%"SCNu64" %"SCNu32"\r\n", l->feed->user_id, l->feed->audio_ssrc);
3766
                                g_strlcat(audio_muxed, temp, 1024);
3767
                        }
3768
                        if(strstr(l->feed->sdp, "m=video")) {
3769
                                video++;
3770
                                g_snprintf(temp, 255, "a=planb:sfu%"SCNu64" %"SCNu32"\r\n", l->feed->user_id, l->feed->video_ssrc);
3771
                                g_strlcat(video_muxed, temp, 1024);
3772
                        }
3773
                }
3774
                ps = ps->next;
3775
        }
3776
        /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
3777
        if(audio) {
3778
                g_strlcat(audio_mline, audio_muxed, 2048);
3779
        }
3780
        if(video) {
3781
                g_strlcat(video_mline, video_muxed, 2048);
3782
        }
3783
        g_snprintf(sdp, 2048, sdp_template,
3784
                janus_get_real_time(),                        /* We need current time here */
3785
                janus_get_real_time(),                        /* We need current time here */
3786
                muxed_listener->room->room_name,        /* Video room name */
3787
                audio_mline,                                        /* Audio m-line */
3788
                video_mline,                                        /* Video m-line */
3789
                data_mline);                                        /* Data channel m-line */
3790
        char *newsdp = g_strdup(sdp);
3791
        if(video) {
3792
                /* Remove useless bandwidth attribute, if any */
3793
                newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
3794
        }
3795
        JANUS_LOG(LOG_VERB, "%s", newsdp);
3796
        json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", newsdp);
3797
        /* How long will the gateway take to push the event? */
3798
        gint64 start = janus_get_monotonic_time();
3799
        int res = gateway->push_event(muxed_listener->session->handle, &janus_videoroom_plugin, transaction, event, jsep);
3800
        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3801
        json_decref(event);
3802
        json_decref(jsep);
3803
        if(res != JANUS_OK) {
3804
                /* TODO Failed to negotiate? We should remove this listener */
3805
        } else {
3806
                /* Let's wait for the setup_media event */
3807
        }
3808
        return 0;
3809
}
3810

    
3811

    
3812
/* Helper to quickly relay RTP packets from publishers to subscribers */
3813
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
3814
        janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
3815
        if(!packet || !packet->data || packet->length < 1) {
3816
                JANUS_LOG(LOG_ERR, "Invalid packet...\n");
3817
                return;
3818
        }
3819
        janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
3820
        if(!listener || !listener->session) {
3821
                // JANUS_LOG(LOG_ERR, "Invalid session...\n");
3822
                return;
3823
        }
3824
        if(listener->paused) {
3825
                // JANUS_LOG(LOG_ERR, "This listener paused the stream...\n");
3826
                return;
3827
        }
3828
        janus_videoroom_session *session = listener->session;
3829
        if(!session || !session->handle) {
3830
                // JANUS_LOG(LOG_ERR, "Invalid session...\n");
3831
                return;
3832
        }
3833
        if(!session->started) {
3834
                // JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
3835
                return;
3836
        }
3837
        
3838
        /* Make sure there hasn't been a publisher switch by checking the SSRC */
3839
        if(packet->is_video) {
3840
                /* Check if this listener is subscribed to this medium */
3841
                if(!listener->video) {
3842
                        /* Nope, don't relay */
3843
                        return;
3844
                }
3845
                if(ntohl(packet->data->ssrc) != listener->context.v_last_ssrc) {
3846
                        /* Publisher switch? Fix sequence numbers and timestamps */
3847
                        listener->context.v_last_ssrc = ntohl(packet->data->ssrc);
3848
                        listener->context.v_base_ts_prev = listener->context.v_last_ts;
3849
                        listener->context.v_base_ts = packet->timestamp;
3850
                        listener->context.v_base_seq_prev = listener->context.v_last_seq;
3851
                        listener->context.v_base_seq = packet->seq_number;
3852
                }
3853
                if(listener->context.v_seq_reset) {
3854
                        /* video_active false-->true? Fix sequence numbers */
3855
                        listener->context.v_seq_reset = FALSE;
3856
                        listener->context.v_base_seq_prev = listener->context.v_last_seq;
3857
                        listener->context.v_base_seq = packet->seq_number;
3858
                }
3859
                /* Compute a coherent timestamp and sequence number */
3860
                listener->context.v_last_ts = (packet->timestamp-listener->context.v_base_ts)
3861
                        + listener->context.v_base_ts_prev+4500;        /* FIXME When switching, we assume 15fps */
3862
                listener->context.v_last_seq = (packet->seq_number-listener->context.v_base_seq)+listener->context.v_base_seq_prev+1;
3863
                /* Update the timestamp and sequence number in the RTP packet, and send it */
3864
                packet->data->timestamp = htonl(listener->context.v_last_ts);
3865
                packet->data->seq_number = htons(listener->context.v_last_seq);
3866
                if(gateway != NULL)
3867
                        gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
3868
                /* Restore the timestamp and sequence number to what the publisher set them to */
3869
                packet->data->timestamp = htonl(packet->timestamp);
3870
                packet->data->seq_number = htons(packet->seq_number);
3871
        } else {
3872
                /* Check if this listener is subscribed to this medium */
3873
                if(!listener->audio) {
3874
                        /* Nope, don't relay */
3875
                        return;
3876
                }
3877
                if(ntohl(packet->data->ssrc) != listener->context.a_last_ssrc) {
3878
                        /* Publisher switch? Fix sequence numbers and timestamps */
3879
                        listener->context.a_last_ssrc = ntohl(packet->data->ssrc);
3880
                        listener->context.a_base_ts_prev = listener->context.a_last_ts;
3881
                        listener->context.a_base_ts = packet->timestamp;
3882
                        listener->context.a_base_seq_prev = listener->context.a_last_seq;
3883
                        listener->context.a_base_seq = packet->seq_number;
3884
                }
3885
                if(listener->context.a_seq_reset) {
3886
                        /* audio_active false-->true? Fix sequence numbers */
3887
                        listener->context.a_seq_reset = FALSE;
3888
                        listener->context.a_base_seq_prev = listener->context.a_last_seq;
3889
                        listener->context.a_base_seq = packet->seq_number;
3890
                }
3891
                /* Compute a coherent timestamp and sequence number */
3892
                listener->context.a_last_ts = (packet->timestamp-listener->context.a_base_ts)
3893
                        + listener->context.a_base_ts_prev+960;        /* FIXME When switching, we assume Opus and so a 960 ts step */
3894
                listener->context.a_last_seq = (packet->seq_number-listener->context.a_base_seq)+listener->context.a_base_seq_prev+1;
3895
                /* Update the timestamp and sequence number in the RTP packet, and send it */
3896
                packet->data->timestamp = htonl(listener->context.a_last_ts);
3897
                packet->data->seq_number = htons(listener->context.a_last_seq);
3898
                if(gateway != NULL)
3899
                        gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
<