Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_videoroom.c @ 78955474

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

    
113
#include "plugin.h"
114

    
115
#include <jansson.h>
116
#include <sofia-sip/sdp.h>
117

    
118
#include "../debug.h"
119
#include "../apierror.h"
120
#include "../config.h"
121
#include "../mutex.h"
122
#include "../rtp.h"
123
#include "../rtcp.h"
124
#include "../record.h"
125
#include "../utils.h"
126
#include <sys/types.h>
127
#include <sys/socket.h>
128

    
129

    
130
/* Plugin information */
131
#define JANUS_VIDEOROOM_VERSION                        6
132
#define JANUS_VIDEOROOM_VERSION_STRING        "0.0.6"
133
#define JANUS_VIDEOROOM_DESCRIPTION                "This is a plugin implementing a videoconferencing SFU (Selective Forwarding Unit) for Janus, that is an audio/video router."
134
#define JANUS_VIDEOROOM_NAME                        "JANUS VideoRoom plugin"
135
#define JANUS_VIDEOROOM_AUTHOR                        "Meetecho s.r.l."
136
#define JANUS_VIDEOROOM_PACKAGE                        "janus.plugin.videoroom"
137

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

    
160
/* Plugin setup */
161
static janus_plugin janus_videoroom_plugin =
162
        JANUS_PLUGIN_INIT (
163
                .init = janus_videoroom_init,
164
                .destroy = janus_videoroom_destroy,
165

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

    
186
/* Plugin creator */
187
janus_plugin *create(void) {
188
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
189
        return &janus_videoroom_plugin;
190
}
191

    
192

    
193
/* Useful stuff */
194
static gint initialized = 0, stopping = 0;
195
static janus_callbacks *gateway = NULL;
196
static GThread *handler_thread;
197
static GThread *watchdog;
198
static su_home_t *sdphome = NULL;
199
static void *janus_videoroom_handler(void *data);
200
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
201
static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data);
202

    
203
typedef enum janus_videoroom_p_type {
204
        janus_videoroom_p_type_none = 0,
205
        janus_videoroom_p_type_subscriber,                        /* Generic listener/subscriber */
206
        janus_videoroom_p_type_subscriber_muxed,        /* Multiplexed listener/subscriber */
207
        janus_videoroom_p_type_publisher,                        /* Participant/publisher */
208
} janus_videoroom_p_type;
209

    
210
typedef struct janus_videoroom_message {
211
        janus_plugin_session *handle;
212
        char *transaction;
213
        json_t *message;
214
        char *sdp_type;
215
        char *sdp;
216
} janus_videoroom_message;
217
static GAsyncQueue *messages = NULL;
218

    
219
static void janus_videoroom_message_free(janus_videoroom_message *msg) {
220
        if(!msg)
221
                return;
222

    
223
        msg->handle = NULL;
224

    
225
        g_free(msg->transaction);
226
        msg->transaction = NULL;
227
        if(msg->message)
228
                json_decref(msg->message);
229
        msg->message = NULL;
230
        g_free(msg->sdp_type);
231
        msg->sdp_type = NULL;
232
        g_free(msg->sdp);
233
        msg->sdp = NULL;
234

    
235
        g_free(msg);
236
}
237

    
238

    
239
typedef struct janus_videoroom {
240
        guint64 room_id;                        /* Unique room ID */
241
        gchar *room_name;                        /* Room description */
242
        gchar *room_secret;                        /* Secret needed to manipulate (e.g., destroy) this room */
243
        gboolean is_private;                        /* Whether this room is 'private' (as in hidden) or not */
244
        int max_publishers;                        /* Maximum number of concurrent publishers */
245
        uint64_t bitrate;                        /* Global bitrate limit */
246
        uint16_t fir_freq;                        /* Regular FIR frequency (0=disabled) */
247
        gboolean record;                        /* Whether the feeds from publishers in this room should be recorded */
248
        char *rec_dir;                                /* Where to save the recordings of this room, if enabled */
249
        gint64 destroyed;                        /* Value to flag the room for destruction, done lazily */
250
        GHashTable *participants;        /* Map of potential publishers (we get listeners from them) */
251
        janus_mutex participants_mutex;/* Mutex to protect room properties */
252
} janus_videoroom;
253
static GHashTable *rooms;
254
static janus_mutex rooms_mutex;
255
static GList *old_rooms;
256
static void janus_videoroom_free(janus_videoroom *room);
257

    
258
typedef struct janus_videoroom_session {
259
        janus_plugin_session *handle;
260
        janus_videoroom_p_type participant_type;
261
        gpointer participant;
262
        gboolean started;
263
        gboolean stopping;
264
        gboolean hangingup;
265
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
266
} janus_videoroom_session;
267
static GHashTable *sessions;
268
static GList *old_sessions;
269
static janus_mutex sessions_mutex;
270

    
271
/* a host whose ports gets streamed rtp packets of the corresponding type. */
272
typedef struct rtp_forwarder {
273
        int is_video;
274
        struct sockaddr_in serv_addr;
275
} rtp_forwarder;
276

    
277
typedef struct janus_videoroom_participant {
278
        janus_videoroom_session *session;
279
        janus_videoroom *room;        /* Room */
280
        guint64 user_id;        /* Unique ID in the room */
281
        gchar *display;        /* Display name (just for fun) */
282
        gchar *sdp;                        /* The SDP this publisher negotiated, if any */
283
        gboolean audio, video, data;                /* Whether audio, video and/or data is going to be sent by this publisher */
284
        guint32 audio_ssrc;                /* Audio SSRC of this publisher */
285
        guint32 video_ssrc;                /* Video SSRC of this publisher */
286
        gboolean audio_active;
287
        gboolean video_active;
288
        gboolean firefox;        /* We send Firefox users a different kind of FIR */
289
        uint64_t bitrate;
290
        gint64 remb_startup;/* Incremental changes on REMB to reach the target at startup */
291
        gint64 remb_latest;        /* Time of latest sent REMB (to avoid flooding) */
292
        gint64 fir_latest;        /* Time of latest sent FIR (to avoid flooding) */
293
        gint fir_seq;                /* FIR sequence number */
294
        gboolean recording_active;        /* Whether this publisher has to be recorded or not */
295
        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 */
296
        janus_recorder *arc;        /* The Janus recorder instance for this publisher's audio, if enabled */
297
        janus_recorder *vrc;        /* The Janus recorder instance for this publisher's video, if enabled */
298
        GSList *listeners;
299
        janus_mutex listeners_mutex;
300
        GHashTable *rtp_forwarders;
301
        janus_mutex rtp_forwarders_mutex;
302
        int udp_sock; /* The udp socket on which to forward rtp packets */
303
} janus_videoroom_participant;
304
static void janus_videoroom_participant_free(janus_videoroom_participant *p);
305
static void janus_rtp_forwarder_free_helper(gpointer data);
306
static guint32 janus_rtp_forwarder_add_helper(janus_videoroom_participant *p, const gchar* host, int port, int is_video);
307
typedef struct janus_videoroom_listener_context {
308
        /* Needed to fix seq and ts in case of publisher switching */
309
        uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev,
310
                        v_last_ssrc, v_last_ts, v_base_ts, v_base_ts_prev;
311
        uint16_t a_last_seq, a_base_seq, a_base_seq_prev,
312
                        v_last_seq, v_base_seq, v_base_seq_prev;
313
} janus_videoroom_listener_context;
314

    
315
typedef struct janus_videoroom_listener {
316
        janus_videoroom_session *session;
317
        janus_videoroom *room;        /* Room */
318
        janus_videoroom_participant *feed;        /* Participant this listener is subscribed to */
319
        janus_videoroom_listener_context context;        /* Needed in case there are publisher switches on this listener */
320
        gboolean audio, video, data;                /* Whether audio, video and/or data must be sent to this publisher */
321
        struct janus_videoroom_listener_muxed *parent;        /* Overall subscriber, if this is a sub-listener in a Multiplexed one */
322
        gboolean paused;
323
} janus_videoroom_listener;
324
static void janus_videoroom_listener_free(janus_videoroom_listener *l);
325

    
326
typedef struct janus_videoroom_listener_muxed {
327
        janus_videoroom_session *session;
328
        janus_videoroom *room;        /* Room */
329
        GSList *listeners;        /* List of listeners (as a Multiplexed listener can be subscribed to more publishers at the same time) */
330
        janus_mutex listeners_mutex;
331
} janus_videoroom_listener_muxed;
332
static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l);
333

    
334
typedef struct janus_videoroom_rtp_relay_packet {
335
        rtp_header *data;
336
        gint length;
337
        gint is_video;
338
        uint32_t timestamp;
339
        uint16_t seq_number;
340
} janus_videoroom_rtp_relay_packet;
341

    
342
typedef struct janus_videoroom_data_relay_packet {
343
        char *data;
344
        gint length;
345
} janus_videoroom_data_relay_packet;
346

    
347
/* SDP offer/answer templates */
348
#define OPUS_PT                111
349
#define VP8_PT                100
350
#define sdp_template \
351
                "v=0\r\n" \
352
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */ \
353
                "s=%s\r\n"                                                        /* Video room name */ \
354
                "t=0 0\r\n" \
355
                "%s%s%s"                                /* Audio, video and/or data channel m-lines */
356
#define sdp_a_template \
357
                "m=audio 1 RTP/SAVPF %d\r\n"                /* Opus payload type */ \
358
                "c=IN IP4 1.1.1.1\r\n" \
359
                "a=%s\r\n"                                                        /* Media direction */ \
360
                "a=rtpmap:%d opus/48000/2\r\n"                /* Opus payload type */
361
#define sdp_v_template \
362
                "m=video 1 RTP/SAVPF %d\r\n"                /* VP8 payload type */ \
363
                "c=IN IP4 1.1.1.1\r\n" \
364
                "b=AS:%d\r\n"                                                /* Bandwidth */ \
365
                "a=%s\r\n"                                                        /* Media direction */ \
366
                "a=rtpmap:%d VP8/90000\r\n"                        /* VP8 payload type */ \
367
                "a=rtcp-fb:%d ccm fir\r\n"                        /* VP8 payload type */ \
368
                "a=rtcp-fb:%d nack\r\n"                                /* VP8 payload type */ \
369
                "a=rtcp-fb:%d nack pli\r\n"                        /* VP8 payload type */ \
370
                "a=rtcp-fb:%d goog-remb\r\n"                /* VP8 payload type */
371
#define sdp_d_template \
372
                "m=application 1 DTLS/SCTP 5000\r\n" \
373
                "c=IN IP4 1.1.1.1\r\n" \
374
                "a=sctpmap:5000 webrtc-datachannel 16\r\n"
375

    
376

    
377
/* Error codes */
378
#define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR                499
379
#define JANUS_VIDEOROOM_ERROR_NO_MESSAGE                421
380
#define JANUS_VIDEOROOM_ERROR_INVALID_JSON                422
381
#define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST        423
382
#define JANUS_VIDEOROOM_ERROR_JOIN_FIRST                424
383
#define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED        425
384
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM                426
385
#define JANUS_VIDEOROOM_ERROR_ROOM_EXISTS                427
386
#define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED                428
387
#define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT        429
388
#define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT        430
389
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE        431
390
#define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL        432
391
#define JANUS_VIDEOROOM_ERROR_UNAUTHORIZED                433
392
#define JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED        434
393
#define JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED                435
394
#define JANUS_VIDEOROOM_ERROR_ID_EXISTS                        436
395
#define JANUS_VIDEOROOM_ERROR_INVALID_SDP                437
396

    
397

    
398
/* Multiplexing helpers */
399
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
400
int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction);
401
int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, char *event_text);
402

    
403
static guint32 janus_rtp_forwarder_add_helper(janus_videoroom_participant* p, const gchar* host, int port, int is_video) {
404
        if(!p || !host) {
405
                return 0;
406
        }
407
        rtp_forwarder* forward = malloc(sizeof(rtp_forwarder));
408
        forward->is_video = is_video;
409
        forward->serv_addr.sin_family = AF_INET;
410
        inet_pton(AF_INET, host, &(forward->serv_addr.sin_addr));
411
        forward->serv_addr.sin_port = htons(port);
412
        janus_mutex_lock(&p->rtp_forwarders_mutex);
413
        guint32 stream_id = g_random_int();
414
        while(g_hash_table_lookup(p->rtp_forwarders, GUINT_TO_POINTER(stream_id)) != NULL) {
415
                stream_id = g_random_int();
416
        }
417
        g_hash_table_insert(p->rtp_forwarders, GUINT_TO_POINTER(stream_id), forward);
418
        janus_mutex_unlock(&p->rtp_forwarders_mutex);
419
        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);
420
        return stream_id;
421
}
422

    
423

    
424
/* Convenience function for freeing a session */
425
static void session_free(gpointer data) {
426
        if(data) {
427
                janus_videoroom_session* session = (janus_videoroom_session*)data;
428
                switch(session->participant_type) {
429
                case janus_videoroom_p_type_publisher: 
430
                        janus_videoroom_participant_free(session->participant);
431
                        break;   
432
                case janus_videoroom_p_type_subscriber:
433
                        janus_videoroom_listener_free(session->participant);
434
                        break;
435
                case janus_videoroom_p_type_subscriber_muxed:
436
                        janus_videoroom_muxed_listener_free(session->participant);
437
                        break;
438
                default:
439
                        break;
440
                }
441
                session->handle = NULL;
442
                g_free(session);
443
                session = NULL;
444
        }
445
}
446

    
447
static void janus_rtp_forwarder_free_helper(gpointer data) {
448
        if(data) {
449
                rtp_forwarder* forward = (rtp_forwarder*)data;
450
                if(forward) {
451
                        free(forward);
452
                        forward = NULL;
453
                }
454
        }
455
}
456

    
457
/* Convenience wrapper function for session_free that corresponds to GHRFunc() format for hash table cleanup */
458
static gboolean session_hash_table_remove(gpointer key, gpointer value, gpointer not_used) {
459
        if(value) {
460
                session_free(value);
461
        }
462
        return TRUE;
463
}
464

    
465
/* VideoRoom watchdog/garbage collector (sort of) */
466
void *janus_videoroom_watchdog(void *data);
467
void *janus_videoroom_watchdog(void *data) {
468
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog started\n");
469
        gint64 now = 0, room_now = 0;
470
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
471
                janus_mutex_lock(&sessions_mutex);
472
                /* Iterate on all the participants/listeners and check if we need to remove any of them */
473
                now = janus_get_monotonic_time();
474
                if(old_sessions != NULL) {
475
                        GList *sl = old_sessions;
476
                        JANUS_LOG(LOG_HUGE, "Checking %d old VideoRoom sessions...\n", g_list_length(old_sessions));
477
                        while(sl) {
478
                                janus_videoroom_session *session = (janus_videoroom_session *)sl->data;
479
                                /* If we are stopping, their is no point to continue to iterate */
480
                                if(!initialized || stopping) {
481
                                        break;
482
                                }
483
                                if(!session) {
484
                                        sl = sl->next;
485
                                        continue;
486
                                }
487
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
488
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
489
                                        JANUS_LOG(LOG_VERB, "Freeing old VideoRoom session\n");
490
                                        GList *rm = sl->next;
491
                                        old_sessions = g_list_delete_link(old_sessions, sl);
492
                                        sl = rm;
493
                                        g_hash_table_steal(sessions, session->handle);
494
                                        session_free(session);
495
                                        continue;
496
                                }
497
                                sl = sl->next;
498
                        }
499
                }
500
                janus_mutex_unlock(&sessions_mutex);
501
                janus_mutex_lock(&rooms_mutex);
502
                if(old_rooms != NULL) {
503
                        GList *rl = old_rooms;
504
                        room_now = janus_get_monotonic_time();
505
                        while(rl) {
506
                                janus_videoroom* room = (janus_videoroom*)rl->data;
507
                                if(!initialized || stopping){
508
                                        break;
509
                                }
510
                                if(!room) {
511
                                        rl = rl->next;
512
                                        continue;
513
                                }
514
                                if(room_now - room->destroyed >= 5*G_USEC_PER_SEC) {
515
                                        GList *rm = rl->next;
516
                                        old_rooms = g_list_delete_link(old_rooms, rl);
517
                                        rl = rm;
518
                                        g_hash_table_remove(rooms, GUINT_TO_POINTER(room->room_id));
519
                                        continue;
520
                                }
521
                                rl = rl->next;
522
                        }
523
                }
524
                janus_mutex_unlock(&rooms_mutex);
525
                g_usleep(500000);
526
        }
527
        JANUS_LOG(LOG_INFO, "VideoRoom watchdog stopped\n");
528
        return NULL;
529
}
530

    
531

    
532
/* Plugin implementation */
533
int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
534
        if(g_atomic_int_get(&stopping)) {
535
                /* Still stopping from before */
536
                return -1;
537
        }
538
        if(callback == NULL || config_path == NULL) {
539
                /* Invalid arguments */
540
                return -1;
541
        }
542
        sdphome = su_home_new(sizeof(su_home_t));
543
        if(su_home_init(sdphome) < 0) {
544
                JANUS_LOG(LOG_FATAL, "Ops, error setting up sofia-sdp?\n");
545
                return -1;
546
        }
547

    
548
        /* Read configuration */
549
        char filename[255];
550
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
551
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
552
        janus_config *config = janus_config_parse(filename);
553
        if(config != NULL)
554
                janus_config_print(config);
555

    
556
        rooms = g_hash_table_new_full(NULL, NULL, NULL,
557
                                      (GDestroyNotify) janus_videoroom_free);
558
        janus_mutex_init(&rooms_mutex);
559
        sessions = g_hash_table_new(NULL, NULL);
560
        janus_mutex_init(&sessions_mutex);
561

    
562
        messages = g_async_queue_new_full((GDestroyNotify) janus_videoroom_message_free);
563

    
564
        /* This is the callback we'll need to invoke to contact the gateway */
565
        gateway = callback;
566

    
567
        /* Parse configuration to populate the rooms list */
568
        if(config != NULL) {
569
                janus_config_category *cat = janus_config_get_categories(config);
570
                while(cat != NULL) {
571
                        if(cat->name == NULL) {
572
                                cat = cat->next;
573
                                continue;
574
                        }
575
                        JANUS_LOG(LOG_VERB, "Adding video room '%s'\n", cat->name);
576
                        janus_config_item *desc = janus_config_get_item(cat, "description");
577
                        janus_config_item *priv = janus_config_get_item(cat, "is_private");
578
                        janus_config_item *secret = janus_config_get_item(cat, "secret");
579
                        janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
580
                        janus_config_item *maxp = janus_config_get_item(cat, "publishers");
581
                        janus_config_item *firfreq = janus_config_get_item(cat, "fir_freq");
582
                        janus_config_item *record = janus_config_get_item(cat, "record");
583
                        janus_config_item *rec_dir = janus_config_get_item(cat, "rec_dir");
584
                        /* Create the video room */
585
                        janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
586
                        if(videoroom == NULL) {
587
                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
588
                                continue;
589
                        }
590
                        videoroom->room_id = atoi(cat->name);
591
                        char *description = NULL;
592
                        if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
593
                                description = g_strdup(desc->value);
594
                        else
595
                                description = g_strdup(cat->name);
596
                        if(description == NULL) {
597
                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
598
                                continue;
599
                        }
600
                        videoroom->room_name = description;
601
                        if(secret != NULL && secret->value != NULL) {
602
                                videoroom->room_secret = g_strdup(secret->value);
603
                        }
604
                        videoroom->is_private = priv && priv->value && janus_is_true(priv->value);
605
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
606
                        if(maxp != NULL && maxp->value != NULL)
607
                                videoroom->max_publishers = atol(maxp->value);
608
                        if(videoroom->max_publishers < 0)
609
                                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
610
                        videoroom->bitrate = 0;
611
                        if(bitrate != NULL && bitrate->value != NULL)
612
                                videoroom->bitrate = atol(bitrate->value);
613
                        if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
614
                                videoroom->bitrate = 64000;        /* Don't go below 64k */
615
                        videoroom->fir_freq = 0;
616
                        if(firfreq != NULL && firfreq->value != NULL)
617
                                videoroom->fir_freq = atol(firfreq->value);
618
                        if(record && record->value) {
619
                                videoroom->record = janus_is_true(record->value);
620
                                if(rec_dir && rec_dir->value) {
621
                                        videoroom->rec_dir = g_strdup(rec_dir->value);
622
                                }
623
                        }
624
                        videoroom->destroyed = 0;
625
                        janus_mutex_init(&videoroom->participants_mutex);
626
                        videoroom->participants = g_hash_table_new(NULL, NULL);
627
                        janus_mutex_lock(&rooms_mutex);
628
                        g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
629
                        janus_mutex_unlock(&rooms_mutex);
630
                        JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, secret: %s)\n", videoroom->room_id, videoroom->room_name, videoroom->is_private ? "private" : "public", videoroom->room_secret ? videoroom->room_secret : "no secret");
631
                        if(videoroom->record) {
632
                                JANUS_LOG(LOG_VERB, "  -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
633
                        }
634
                        cat = cat->next;
635
                }
636
                /* Done */
637
                janus_config_destroy(config);
638
                config = NULL;
639
        }
640

    
641
        /* Show available rooms */
642
        janus_mutex_lock(&rooms_mutex);
643
        GHashTableIter iter;
644
        gpointer value;
645
        g_hash_table_iter_init(&iter, rooms);
646
        while (g_hash_table_iter_next(&iter, NULL, &value)) {
647
                janus_videoroom *vr = value;
648
                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);
649
        }
650
        janus_mutex_unlock(&rooms_mutex);
651

    
652
        g_atomic_int_set(&initialized, 1);
653

    
654
        GError *error = NULL;
655
        /* Start the sessions watchdog */
656
        watchdog = g_thread_try_new("vroom watchdog", &janus_videoroom_watchdog, NULL, &error);
657
        if(error != NULL) {
658
                g_atomic_int_set(&initialized, 0);
659
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom watchdog thread...\n", error->code, error->message ? error->message : "??");
660
                return -1;
661
        }
662
        /* Launch the thread that will handle incoming messages */
663
        handler_thread = g_thread_try_new("janus videoroom handler", janus_videoroom_handler, NULL, &error);
664
        if(error != NULL) {
665
                g_atomic_int_set(&initialized, 0);
666
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom handler thread...\n", error->code, error->message ? error->message : "??");
667
                return -1;
668
        }
669
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
670
        return 0;
671
}
672

    
673
void janus_videoroom_destroy(void) {
674
        if(!g_atomic_int_get(&initialized))
675
                return;
676
        g_atomic_int_set(&stopping, 1);
677
        if(handler_thread != NULL) {
678
                g_thread_join(handler_thread);
679
                handler_thread = NULL;
680
        }
681
        if(watchdog != NULL) {
682
                g_thread_join(watchdog);
683
                watchdog = NULL;
684
        }
685
        su_home_deinit(sdphome);
686
        su_home_unref(sdphome);
687
        sdphome = NULL;
688

    
689
        /* FIXME We should destroy the sessions cleanly */
690
        janus_mutex_lock(&sessions_mutex);
691
        g_hash_table_foreach_remove(sessions, (GHRFunc)session_hash_table_remove, NULL);
692
        g_hash_table_destroy(sessions);
693
        sessions = NULL;
694
        janus_mutex_unlock(&sessions_mutex);
695

    
696
        janus_mutex_lock(&rooms_mutex);
697

    
698
        g_hash_table_destroy(rooms);
699
        rooms = NULL;
700
        janus_mutex_unlock(&rooms_mutex);
701
        janus_mutex_destroy(&rooms_mutex);
702

    
703
        g_async_queue_unref(messages);
704
        messages = NULL;
705

    
706
        g_atomic_int_set(&initialized, 0);
707
        g_atomic_int_set(&stopping, 0);
708
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
709
}
710

    
711
int janus_videoroom_get_api_compatibility(void) {
712
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
713
        return JANUS_PLUGIN_API_VERSION;
714
}
715

    
716
int janus_videoroom_get_version(void) {
717
        return JANUS_VIDEOROOM_VERSION;
718
}
719

    
720
const char *janus_videoroom_get_version_string(void) {
721
        return JANUS_VIDEOROOM_VERSION_STRING;
722
}
723

    
724
const char *janus_videoroom_get_description(void) {
725
        return JANUS_VIDEOROOM_DESCRIPTION;
726
}
727

    
728
const char *janus_videoroom_get_name(void) {
729
        return JANUS_VIDEOROOM_NAME;
730
}
731

    
732
const char *janus_videoroom_get_author(void) {
733
        return JANUS_VIDEOROOM_AUTHOR;
734
}
735

    
736
const char *janus_videoroom_get_package(void) {
737
        return JANUS_VIDEOROOM_PACKAGE;
738
}
739

    
740
void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
741
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
742
                *error = -1;
743
                return;
744
        }        
745
        janus_videoroom_session *session = (janus_videoroom_session *)calloc(1, sizeof(janus_videoroom_session));
746
        if(session == NULL) {
747
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
748
                *error = -2;
749
                return;
750
        }
751
        session->handle = handle;
752
        session->participant_type = janus_videoroom_p_type_none;
753
        session->participant = NULL;
754
        session->destroyed = 0;
755
        handle->plugin_handle = session;
756
        janus_mutex_lock(&sessions_mutex);
757
        g_hash_table_insert(sessions, handle, session);
758
        janus_mutex_unlock(&sessions_mutex);
759

    
760
        return;
761
}
762

    
763
void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
764
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
765
                *error = -1;
766
                return;
767
        }        
768
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle; 
769
        if(!session) {
770
                JANUS_LOG(LOG_ERR, "No VideoRoom session associated with this handle...\n");
771
                *error = -2;
772
                return;
773
        }
774
        if(session->destroyed) {
775
                JANUS_LOG(LOG_WARN, "VideoRoom session already marked as destroyed...\n");
776
                return;
777
        }
778
        JANUS_LOG(LOG_VERB, "Removing VideoRoom session...\n");
779
        /* Cleaning up and removing the session is done in a lazy way */
780
        janus_mutex_lock(&sessions_mutex);
781
        if(!session->destroyed) {
782
                /* Any related WebRTC PeerConnection is not available anymore either */
783
                janus_videoroom_hangup_media(handle);
784
                session->destroyed = janus_get_monotonic_time();
785
                old_sessions = g_list_append(old_sessions, session);
786
                if(session->participant_type == janus_videoroom_p_type_publisher) {
787
                        /* Get rid of publisher */
788
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
789
                        participant->audio = FALSE;
790
                        participant->video = FALSE;
791
                        participant->data = FALSE;
792
                        participant->audio_active = FALSE;
793
                        participant->video_active = FALSE;
794
                        participant->recording_active = FALSE;
795
                        if(participant->recording_base)
796
                                g_free(participant->recording_base);
797
                        participant->recording_base = NULL;
798
                        json_t *event = json_object();
799
                        json_object_set_new(event, "videoroom", json_string("event"));
800
                        json_object_set_new(event, "room", json_integer(participant->room->room_id));
801
                        json_object_set_new(event, "leaving", json_integer(participant->user_id));
802
                        char *leaving_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
803
                        json_decref(event);
804
                        GHashTableIter iter;
805
                        gpointer value;
806
                        /* we need to check if the room still exists, may have been destroyed already */
807
                        if(participant->room) {
808
                                if(!participant->room->destroyed) {
809
                                        janus_mutex_lock(&participant->room->participants_mutex);
810
                                        g_hash_table_iter_init(&iter, participant->room->participants);
811
                                        while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
812
                                                janus_videoroom_participant *p = value;
813
                                                if(p == participant) {
814
                                                        continue;        /* Skip the leaving publisher itself */
815
                                                }
816
                                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
817
                                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
818
                                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
819
                                        }
820
                                        g_hash_table_remove(participant->room->participants, GUINT_TO_POINTER(participant->user_id));
821
                                        janus_mutex_unlock(&participant->room->participants_mutex);
822
                                }
823
                        }
824
                        g_free(leaving_text);
825
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
826
                        /* Detaching this listener from its publisher is already done by hangup_media */
827
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
828
                        /* Detaching this listener from its publishers is already done by hangup_media */
829
                }
830
        }
831
        janus_mutex_unlock(&sessions_mutex);
832

    
833
        return;
834
}
835

    
836
char *janus_videoroom_query_session(janus_plugin_session *handle) {
837
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
838
                return NULL;
839
        }        
840
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
841
        if(!session) {
842
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
843
                return NULL;
844
        }
845
        /* Show the participant/room info, if any */
846
        json_t *info = json_object();
847
        if(session->participant) {
848
                if(session->participant_type == janus_videoroom_p_type_none) {
849
                        json_object_set_new(info, "type", json_string("none"));
850
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
851
                        json_object_set_new(info, "type", json_string("publisher"));
852
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
853
                        if(participant) {
854
                                janus_videoroom *room = participant->room; 
855
                                json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
856
                                json_object_set_new(info, "id", json_integer(participant->user_id));
857
                                if(participant->display)
858
                                        json_object_set_new(info, "display", json_string(participant->display));
859
                                if(participant->listeners)
860
                                        json_object_set_new(info, "viewers", json_integer(g_slist_length(participant->listeners)));
861
                                json_t *media = json_object();
862
                                json_object_set_new(media, "audio", json_integer(participant->audio));
863
                                json_object_set_new(media, "video", json_integer(participant->video));
864
                                json_object_set_new(media, "data", json_integer(participant->data));
865
                                json_object_set_new(info, "media", media);
866
                        }
867
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
868
                        json_object_set_new(info, "type", json_string("listener"));
869
                        janus_videoroom_listener *participant = (janus_videoroom_listener *)session->participant;
870
                        if(participant) {
871
                                janus_videoroom_participant *feed = (janus_videoroom_participant *)participant->feed;
872
                                if(feed) {
873
                                        janus_videoroom *room = feed->room; 
874
                                        json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
875
                                        json_object_set_new(info, "feed_id", json_integer(feed->user_id));
876
                                        if(feed->display)
877
                                                json_object_set_new(info, "feed_display", json_string(feed->display));
878
                                }
879
                                json_t *media = json_object();
880
                                json_object_set_new(media, "audio", json_integer(participant->audio));
881
                                json_object_set_new(media, "video", json_integer(participant->video));
882
                                json_object_set_new(media, "data", json_integer(participant->data));
883
                                json_object_set_new(info, "media", media);
884
                        }
885
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
886
                        json_object_set_new(info, "type", json_string("muxed-listener"));
887
                        /* TODO */
888
                }
889
        }
890
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
891
        char *info_text = json_dumps(info, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
892
        json_decref(info);
893
        return info_text;
894
}
895

    
896
struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
897
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
898
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized");
899
        
900
        /* Pre-parse the message */
901
        int error_code = 0;
902
        char error_cause[512];
903
        json_t *root = NULL;
904
        json_t *response = NULL;
905

    
906
        if(message == NULL) {
907
                JANUS_LOG(LOG_ERR, "No message??\n");
908
                error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
909
                g_snprintf(error_cause, 512, "%s", "No message??");
910
                goto error;
911
        }
912
        JANUS_LOG(LOG_VERB, "Handling message: %s\n", message);
913

    
914
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
915
        if(!session) {
916
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
917
                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
918
                g_snprintf(error_cause, 512, "%s", "session associated with this handle...");
919
                goto error;
920
        }
921
        if(session->destroyed) {
922
                JANUS_LOG(LOG_ERR, "Session has already been marked as destroyed...\n");
923
                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
924
                g_snprintf(error_cause, 512, "%s", "Session has already been marked as destroyed...");
925
                goto error;
926
        }
927
        json_error_t error;
928
        root = json_loads(message, 0, &error);
929
        if(!root) {
930
                JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
931
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
932
                g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text);
933
                goto error;
934
        }
935
        if(!json_is_object(root)) {
936
                JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
937
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
938
                g_snprintf(error_cause, 512, "JSON error: not an object");
939
                goto error;
940
        }
941
        /* Get the request first */
942
        json_t *request = json_object_get(root, "request");
943
        if(!request) {
944
                JANUS_LOG(LOG_ERR, "Missing element (request)\n");
945
                error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
946
                g_snprintf(error_cause, 512, "Missing element (request)");
947
                goto error;
948
        }
949
        if(!json_is_string(request)) {
950
                JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n");
951
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
952
                g_snprintf(error_cause, 512, "Invalid element (request should be a string)");
953
                goto error;
954
        }
955
        /* Some requests ('create', 'destroy', 'exists', 'list') can be handled synchronously */
956
        const char *request_text = json_string_value(request);
957
        if(!strcasecmp(request_text, "create")) {
958
                /* Create a new videoroom */
959
                JANUS_LOG(LOG_VERB, "Creating a new videoroom\n");
960
                json_t *desc = json_object_get(root, "description");
961
                if(desc && !json_is_string(desc)) {
962
                        JANUS_LOG(LOG_ERR, "Invalid element (description should be a string)\n");
963
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
964
                        g_snprintf(error_cause, 512, "Invalid element (description should be a string)");
965
                        goto error;
966
                }
967
                json_t *is_private = json_object_get(root, "is_private");
968
                if(is_private && !json_is_boolean(is_private)) {
969
                        JANUS_LOG(LOG_ERR, "Invalid element (is_private should be a boolean)\n");
970
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
971
                        g_snprintf(error_cause, 512, "Invalid value (is_private should be a boolean)");
972
                        goto error;
973
                }
974
                json_t *secret = json_object_get(root, "secret");
975
                if(secret && !json_is_string(secret)) {
976
                        JANUS_LOG(LOG_ERR, "Invalid element (secret should be a string)\n");
977
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
978
                        g_snprintf(error_cause, 512, "Invalid element (secret should be a string)");
979
                        goto error;
980
                }
981
                json_t *bitrate = json_object_get(root, "bitrate");
982
                if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) {
983
                        JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n");
984
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
985
                        g_snprintf(error_cause, 512, "Invalid element (bitrate should be a positive integer)");
986
                        goto error;
987
                }
988
                json_t *fir_freq = json_object_get(root, "fir_freq");
989
                if(fir_freq && (!json_is_integer(fir_freq) || json_integer_value(fir_freq) < 0)) {
990
                        JANUS_LOG(LOG_ERR, "Invalid element (fir_freq should be a positive integer)\n");
991
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
992
                        g_snprintf(error_cause, 512, "Invalid element (fir_freq should be a positive integer)");
993
                        goto error;
994
                }
995
                json_t *publishers = json_object_get(root, "publishers");
996
                if(publishers && (!json_is_integer(publishers) || json_integer_value(publishers) < 0)) {
997
                        JANUS_LOG(LOG_ERR, "Invalid element (publishers should be a positive integer)\n");
998
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
999
                        g_snprintf(error_cause, 512, "Invalid element (publishers should be a positive integer)");
1000
                        goto error;
1001
                }
1002
                json_t *record = json_object_get(root, "record");
1003
                if(record && !json_is_boolean(record)) {
1004
                        JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n");
1005
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1006
                        g_snprintf(error_cause, 512, "Invalid element (record should be a boolean)");
1007
                        goto error;
1008
                }
1009
                json_t *rec_dir = json_object_get(root, "rec_dir");
1010
                if(rec_dir && !json_is_string(rec_dir)) {
1011
                        JANUS_LOG(LOG_ERR, "Invalid element (rec_dir should be a string)\n");
1012
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1013
                        g_snprintf(error_cause, 512, "Invalid element (rec_dir should be a string)");
1014
                        goto error;
1015
                }
1016
                guint64 room_id = 0;
1017
                json_t *room = json_object_get(root, "room");
1018
                if(room && (!json_is_integer(room) || json_integer_value(room) < 0)) {
1019
                        JANUS_LOG(LOG_ERR, "Invalid element (room should be a positive integer)\n");
1020
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1021
                        g_snprintf(error_cause, 512, "Invalid element (room should be a positive integer)");
1022
                        goto error;
1023
                } else {
1024
                        room_id = json_integer_value(room);
1025
                        if(room_id == 0) {
1026
                                JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
1027
                        }
1028
                }
1029
                janus_mutex_lock(&rooms_mutex);
1030
                if(room_id > 0) {
1031
                        /* Let's make sure the room doesn't exist already */
1032
                        if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
1033
                                /* It does... */
1034
                                janus_mutex_unlock(&rooms_mutex);
1035
                                JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
1036
                                error_code = JANUS_VIDEOROOM_ERROR_ROOM_EXISTS;
1037
                                g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
1038
                                goto error;
1039
                        }
1040
                }
1041
                /* Create the room */
1042
                janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
1043
                if(videoroom == NULL) {
1044
                        janus_mutex_unlock(&rooms_mutex);
1045
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
1046
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1047
                        g_snprintf(error_cause, 512, "Memory error");
1048
                        goto error;
1049
                }
1050
                /* Generate a random ID */
1051
                if(room_id == 0) {
1052
                        while(room_id == 0) {
1053
                                room_id = g_random_int();
1054
                                if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
1055
                                        /* Room ID already taken, try another one */
1056
                                        room_id = 0;
1057
                                }
1058
                        }
1059
                }
1060
                videoroom->room_id = room_id;
1061
                char *description = NULL;
1062
                if(desc != NULL && strlen(json_string_value(desc)) > 0) {
1063
                        description = g_strdup(json_string_value(desc));
1064
                } else {
1065
                        char roomname[255];
1066
                        g_snprintf(roomname, 255, "Room %"SCNu64"", videoroom->room_id);
1067
                        description = g_strdup(roomname);
1068
                }
1069
                if(description == NULL) {
1070
                        janus_mutex_unlock(&rooms_mutex);
1071
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
1072
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1073
                        g_snprintf(error_cause, 512, "Memory error");
1074
                        goto error;
1075
                }
1076
                videoroom->room_name = description;
1077
                videoroom->is_private = is_private ? json_is_true(is_private) : FALSE;
1078
                if(secret)
1079
                        videoroom->room_secret = g_strdup(json_string_value(secret));
1080
                videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
1081
                if(publishers)
1082
                        videoroom->max_publishers = json_integer_value(publishers);
1083
                if(videoroom->max_publishers < 0)
1084
                        videoroom->max_publishers = 3;        /* FIXME How should we choose a default? */
1085
                videoroom->bitrate = 0;
1086
                if(bitrate)
1087
                        videoroom->bitrate = json_integer_value(bitrate);
1088
                if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
1089
                        videoroom->bitrate = 64000;        /* Don't go below 64k */
1090
                videoroom->fir_freq = 0;
1091
                if(fir_freq)
1092
                        videoroom->fir_freq = json_integer_value(fir_freq);
1093
                if(record) {
1094
                        videoroom->record = json_is_true(record);
1095
                        if(videoroom->record && rec_dir) {
1096
                                videoroom->rec_dir = g_strdup(json_string_value(rec_dir));
1097
                        }
1098
                }
1099
                videoroom->destroyed = 0;
1100
                janus_mutex_init(&videoroom->participants_mutex);
1101
                videoroom->participants = g_hash_table_new(NULL, NULL);
1102
                JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, secret: %s)\n", videoroom->room_id, videoroom->room_name, videoroom->is_private ? "private" : "public", videoroom->room_secret ? videoroom->room_secret : "no secret");
1103
                if(videoroom->record) {
1104
                        JANUS_LOG(LOG_VERB, "  -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
1105
                }
1106
                /* Show updated rooms list */
1107
                GHashTableIter iter;
1108
                gpointer value;
1109
                g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
1110
                g_hash_table_iter_init(&iter, rooms);
1111
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1112
                        janus_videoroom *vr = value;
1113
                        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);
1114
                }
1115
                janus_mutex_unlock(&rooms_mutex);
1116
                /* Send info back */
1117
                response = json_object();
1118
                json_object_set_new(response, "videoroom", json_string("created"));
1119
                json_object_set_new(response, "room", json_integer(videoroom->room_id));
1120
                goto plugin_response;
1121
        } else if(!strcasecmp(request_text, "destroy")) {
1122
                JANUS_LOG(LOG_VERB, "Attempt to destroy an existing videoroom room\n");
1123
                json_t *room = json_object_get(root, "room");
1124
                if(!room) {
1125
                        JANUS_LOG(LOG_ERR, "Missing element (room)\n");
1126
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1127
                        g_snprintf(error_cause, 512, "Missing element (room)");
1128
                        goto error;
1129
                }
1130
                if(!json_is_integer(room) || json_integer_value(room) < 0) {
1131
                        JANUS_LOG(LOG_ERR, "Invalid element (room should be a positive integer)\n");
1132
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1133
                        g_snprintf(error_cause, 512, "Invalid element (room should be a positive integer)");
1134
                        goto error;
1135
                }
1136
                guint64 room_id = json_integer_value(room);
1137
                janus_mutex_lock(&rooms_mutex);
1138
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1139
                if(videoroom == NULL) {
1140
                        janus_mutex_unlock(&rooms_mutex);
1141
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1142
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1143
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1144
                        goto error;
1145
                }
1146
                
1147
                if(videoroom->destroyed) {
1148
                        janus_mutex_unlock(&rooms_mutex)
1149
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", videoroom->room_id);
1150
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1151
                        g_snprintf(error_cause, 512, "Videoroom (%"SCNu64")", videoroom->room_id);
1152
                        goto error;
1153
                }
1154
                if(videoroom->room_secret) {
1155
                        /* A secret is required for this action */
1156
                        json_t *secret = json_object_get(root, "secret");
1157
                        if(!secret) {
1158
                                janus_mutex_unlock(&rooms_mutex);
1159
                                JANUS_LOG(LOG_ERR, "Missing element (secret)\n");
1160
                                error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1161
                                g_snprintf(error_cause, 512, "Missing element (secret)");
1162
                                goto error;
1163
                        }
1164
                        if(!json_is_string(secret)) {
1165
                                janus_mutex_unlock(&rooms_mutex);
1166
                                JANUS_LOG(LOG_ERR, "Invalid element (secret should be a string)\n");
1167
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1168
                                g_snprintf(error_cause, 512, "Invalid element (secret should be a string)");
1169
                                goto error;
1170
                        }
1171
                        if(!janus_strcmp_const_time(videoroom->room_secret, json_string_value(secret))) {
1172
                                janus_mutex_unlock(&rooms_mutex);
1173
                                JANUS_LOG(LOG_ERR, "Unauthorized (wrong secret)\n");
1174
                                error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
1175
                                g_snprintf(error_cause, 512, "Unauthorized (wrong secret)");
1176
                                goto error;
1177
                        }
1178
                }
1179
                /* Notify all participants that the fun is over, and that they'll be kicked */
1180
                JANUS_LOG(LOG_VERB, "Notifying all participants\n");
1181
                json_t *destroyed = json_object();
1182
                json_object_set_new(destroyed, "videoroom", json_string("destroyed"));
1183
                json_object_set_new(destroyed, "room", json_integer(videoroom->room_id));
1184
                char *destroyed_text = json_dumps(destroyed, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1185
                GHashTableIter iter;
1186
                gpointer value;
1187
                /* Remove room lazily*/
1188
                videoroom->destroyed = janus_get_monotonic_time();
1189
                old_rooms = g_list_append(old_rooms, videoroom);
1190
                janus_mutex_lock(&videoroom->participants_mutex);
1191
                g_hash_table_iter_init(&iter, videoroom->participants);
1192
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1193
                        janus_videoroom_participant *p = value;
1194
                        if(p && p->session) {
1195
                                /* Notify the user we're going to destroy the room... */
1196
                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, destroyed_text, NULL, NULL);
1197
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1198
                                /* ... and then ask the core to remove the handle */
1199
                                gateway->end_session(p->session->handle);
1200
                        }
1201
                }
1202
                json_decref(destroyed);
1203
                g_free(destroyed_text);
1204
                janus_mutex_unlock(&videoroom->participants_mutex);
1205
                janus_mutex_unlock(&rooms_mutex);
1206
                /* Done */
1207
                response = json_object();
1208
                json_object_set_new(response, "videoroom", json_string("destroyed"));
1209
                json_object_set_new(response, "room", json_integer(room_id));
1210
                goto plugin_response;
1211
        } else if(!strcasecmp(request_text, "list")) {
1212
                /* List all rooms (but private ones) and their details (except for the secret, of course...) */
1213
                json_t *list = json_array();
1214
                JANUS_LOG(LOG_VERB, "Getting the list of video rooms\n");
1215
                janus_mutex_lock(&rooms_mutex);
1216
                GHashTableIter iter;
1217
                gpointer value;
1218
                g_hash_table_iter_init(&iter, rooms);
1219
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1220
                        janus_videoroom *room = value;
1221
                        if(!room)
1222
                                continue;
1223
                        if(room->is_private) {
1224
                                /* Skip private room */
1225
                                JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
1226
                                continue;
1227
                        }
1228
                        if(!room->destroyed) {
1229
                                json_t *rl = json_object();
1230
                                json_object_set_new(rl, "room", json_integer(room->room_id));
1231
                                json_object_set_new(rl, "description", json_string(room->room_name));
1232
                                json_object_set_new(rl, "max_publishers", json_integer(room->max_publishers));
1233
                                json_object_set_new(rl, "bitrate", json_integer(room->bitrate));
1234
                                json_object_set_new(rl, "fir_freq", json_integer(room->fir_freq));
1235
                                json_object_set_new(rl, "record", json_string(room->record ? "true" : "false"));
1236
                                json_object_set_new(rl, "rec_dir", json_string(room->rec_dir));
1237
                                /* TODO: Should we list participants as well? or should there be a separate API call on a specific room for this? */
1238
                                json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
1239
                                json_array_append_new(list, rl);
1240
                        }
1241
                }
1242
                janus_mutex_unlock(&rooms_mutex);
1243
                response = json_object();
1244
                json_object_set_new(response, "videoroom", json_string("success"));
1245
                json_object_set_new(response, "list", list);
1246
                goto plugin_response;
1247
        } else if(!strcasecmp(request_text, "rtp_forward")) {
1248
                json_t *room = json_object_get(root, "room");
1249
                if(!room) {
1250
                        JANUS_LOG(LOG_ERR, "Missing element (room)\n");
1251
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1252
                        g_snprintf(error_cause, 512, "Missing element (room)");
1253
                        goto error;
1254
                }
1255
                if(!json_is_integer(room) || json_integer_value(room) < 0 ) {
1256
                        JANUS_LOG(LOG_ERR, "Invalid element (room should be a positive integer)\n");
1257
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1258
                        g_snprintf(error_cause, 512, "Invalid element (room should be a positive integer)");
1259
                        goto error;
1260
                }
1261
                json_t *pub_id = json_object_get(root, "publisher_id");
1262
                if(!pub_id) {
1263
                        JANUS_LOG(LOG_ERR, "Missing element (publisher_id)\n");
1264
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1265
                        g_snprintf(error_cause, 512, "Missing element (publisher_id)");
1266
                        goto error;
1267
                }
1268
                if(!json_is_integer(pub_id) || json_integer_value(pub_id) < 0) {
1269
                        JANUS_LOG(LOG_ERR, "Invalid element (publisher_id should be a postive integer)\n");
1270
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1271
                        g_snprintf(error_cause, 512, "Invalid element (publisher_id should be a positive integer)");
1272
                        goto error;
1273
                }
1274
                int video_port = -1;
1275
                int audio_port = -1;
1276
                json_t *vid_port = json_object_get(root, "video_port");
1277
                if(vid_port && (!json_is_integer(vid_port) || json_integer_value(vid_port) < 0)) {
1278
                        JANUS_LOG(LOG_ERR, "Invalid element (video_port should be a positive integer)\n");
1279
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1280
                        g_snprintf(error_cause, 512, "Invalid element (video_port should be a positive integer)");
1281
                        goto error;
1282
                }
1283
                if(vid_port) {
1284
                        video_port = json_integer_value(vid_port);
1285
                }
1286
                json_t *au_port = json_object_get(root, "audio_port");
1287
                if(au_port && (!json_is_integer(au_port) || json_integer_value(au_port) <0)) {
1288
                        JANUS_LOG(LOG_ERR, "Invalid element (audio_port should be a positive integer)\n");
1289
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1290
                        g_snprintf(error_cause, 512, "Invalid element (audio_port should be a positive integer)");
1291
                        goto error;
1292
                }
1293
                if(au_port) {
1294
                        audio_port = json_integer_value(au_port);
1295
                }
1296
                json_t *json_host = json_object_get(root, "host");
1297
                if(!json_host) {
1298
                        JANUS_LOG(LOG_ERR, "Missing element (host)\n");
1299
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1300
                        g_snprintf(error_cause, 512, "Missing element (host)");
1301
                        goto error;
1302
                }
1303
                if(!json_is_string(json_host)) {
1304
                        JANUS_LOG(LOG_ERR, "Invalid element (host should be a string)\n");
1305
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1306
                        g_snprintf(error_cause, 512, "Invalid element (host should be a string)");
1307
                        goto error;
1308
                }
1309
                
1310
                guint64 room_id = json_integer_value(room);
1311
                guint64 publisher_id = json_integer_value(pub_id);
1312
                const gchar* host = json_string_value(json_host);
1313
                janus_mutex_lock(&rooms_mutex);
1314
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1315
                janus_mutex_unlock(&rooms_mutex);
1316
                if(videoroom == NULL) {
1317
                        janus_mutex_unlock(&rooms_mutex);
1318
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1319
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1320
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1321
                        goto error;
1322
                }
1323
                if(videoroom->destroyed) {
1324
                        janus_mutex_unlock(&rooms_mutex)
1325
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", videoroom->room_id);
1326
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1327
                        g_snprintf(error_cause, 512, "Videoroom (%"SCNu64")", videoroom->room_id);
1328
                        goto error;
1329
                }
1330
                if(videoroom->room_secret) {
1331
                        /* A secret is required for this action */
1332
                        json_t *secret = json_object_get(root, "secret");
1333
                        if(!secret) {
1334
                                janus_mutex_unlock(&rooms_mutex);
1335
                                JANUS_LOG(LOG_ERR, "Missing element (secret)\n");
1336
                                error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1337
                                g_snprintf(error_cause, 512, "Missing element (secret)");
1338
                                goto error;
1339
                        }
1340
                        if(!json_is_string(secret)) {
1341
                                janus_mutex_unlock(&rooms_mutex);
1342
                                JANUS_LOG(LOG_ERR, "Invalid element (secret should be a string)\n");
1343
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1344
                                g_snprintf(error_cause, 512, "Invalid element (secret should be a string)");
1345
                                goto error;
1346
                        }
1347
                        if(!janus_strcmp_const_time(videoroom->room_secret, json_string_value(secret))) {
1348
                                janus_mutex_unlock(&rooms_mutex);
1349
                                JANUS_LOG(LOG_ERR, "Unauthorized (wrong secret)\n");
1350
                                error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
1351
                                g_snprintf(error_cause, 512, "Unauthorized (wrong secret)");
1352
                                goto error;
1353
                        }
1354
                }
1355
                janus_mutex_lock(&videoroom->participants_mutex);
1356
                janus_videoroom_participant* publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(publisher_id));
1357
                if(publisher == NULL) {
1358
                        janus_mutex_unlock(&videoroom->participants_mutex);
1359
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1360
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1361
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1362
                        goto error;
1363
                }
1364
                if(publisher->udp_sock <= 0) {
1365
                        publisher->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
1366
                        if(publisher->udp_sock <= 0) {
1367
                                janus_mutex_unlock(&videoroom->participants_mutex);
1368
                                JANUS_LOG(LOG_ERR, "Could not open UDP socket for rtp stream for publisher (%"SCNu64")\n", publisher_id);
1369
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1370
                                g_snprintf(error_cause, 512, "Could not open UDP socket for rtp stream");
1371
                                goto error;
1372
                        }
1373
                }
1374
                guint32 audio_handle = 0;
1375
                guint32 video_handle = 0;
1376
                if(audio_port > 0) {
1377
                        audio_handle = janus_rtp_forwarder_add_helper(publisher, host, audio_port, 0);
1378
                }
1379
                if(video_port > 0) {
1380
                        video_handle = janus_rtp_forwarder_add_helper(publisher, host, video_port, 1);
1381
                }
1382
                janus_mutex_unlock(&videoroom->participants_mutex);
1383
                response = json_object();
1384
                json_t* rtp_stream = json_object();
1385
                if(audio_handle > 0) {
1386
                        json_object_set_new(rtp_stream, "audio_stream_id", json_integer(audio_handle));
1387
                        json_object_set_new(rtp_stream, "audio", json_integer(audio_port));
1388
                }
1389
                if(video_handle > 0) {
1390
                        json_object_set_new(rtp_stream, "video_stream_id", json_integer(video_handle));
1391
                        json_object_set_new(rtp_stream, "video", json_integer(video_port));
1392
                }
1393
                json_object_set_new(rtp_stream, "host", json_string(host));
1394
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1395
                json_object_set_new(response, "rtp_stream", rtp_stream);
1396
                json_object_set_new(response, "room", json_integer(room_id));
1397
                json_object_set_new(response, "videoroom", json_string("rtp_forward"));
1398
                goto plugin_response;
1399
        } else if(!strcasecmp(request_text, "stop_rtp_forward")) {
1400
                json_t *room = json_object_get(root, "room");
1401
                if(!room) {
1402
                        JANUS_LOG(LOG_ERR, "Missing element (room)\n");
1403
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1404
                        g_snprintf(error_cause, 512, "Missing element (room)");
1405
                        goto error;
1406
                }
1407
                if(!json_is_integer(room) || json_integer_value(room) < 0) {
1408
                        JANUS_LOG(LOG_ERR, "Invalid element (room should be a positive integer)\n");
1409
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1410
                        g_snprintf(error_cause, 512, "Invalid element (room should be a positive integer)");
1411
                        goto error;
1412
                }
1413
                json_t *pub_id = json_object_get(root, "publisher_id");
1414
                if(!pub_id) {
1415
                        JANUS_LOG(LOG_ERR, "Missing element (publisher_id)\n");
1416
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1417
                        g_snprintf(error_cause, 512, "Missing element (publisher_id)");
1418
                        goto error;
1419
                }
1420
                if(!json_is_integer(pub_id) || json_integer_value(pub_id) < 0) {
1421
                        JANUS_LOG(LOG_ERR, "Invalid element (publisher_id should be a positive integer)\n");
1422
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1423
                        g_snprintf(error_cause, 512, "Invalid element (publisher_id should be a positive integer)");
1424
                        goto error;
1425
                }
1426
                json_t *id = json_object_get(root, "stream_id");
1427
                if(!id) {
1428
                        JANUS_LOG(LOG_ERR, "Missing element (stream_id)\n");
1429
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1430
                        g_snprintf(error_cause, 512, "Missing element (stream_id)");
1431
                        goto error;
1432
                }
1433
                if(!json_is_integer(id) || json_integer_value(id) < 0) {
1434
                        JANUS_LOG(LOG_ERR, "Invalid element (stream_id should be a positive integer)\n");
1435
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1436
                        g_snprintf(error_cause, 512, "Invalid element (stream_id should be a positive integer)");
1437
                        goto error;
1438
                }
1439

    
1440
                guint64 room_id = json_integer_value(room);
1441
                guint64 publisher_id = json_integer_value(pub_id);
1442
                int stream_id = json_integer_value(id);
1443
                janus_mutex_lock(&rooms_mutex);
1444
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1445
                janus_mutex_unlock(&rooms_mutex);
1446
                if(videoroom == NULL) {
1447
                        janus_mutex_unlock(&rooms_mutex);
1448
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1449
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1450
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1451
                        goto error;
1452
                }
1453
                if(videoroom->destroyed) {
1454
                        janus_mutex_unlock(&rooms_mutex)
1455
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", videoroom->room_id);
1456
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1457
                        g_snprintf(error_cause, 512, "Videoroom (%"SCNu64")", videoroom->room_id);
1458
                        goto error;
1459
                }
1460
                if(videoroom->room_secret) {
1461
                        /* A secret is required for this action */
1462
                        json_t *secret = json_object_get(root, "secret");
1463
                        if(!secret) {
1464
                                janus_mutex_unlock(&rooms_mutex);
1465
                                JANUS_LOG(LOG_ERR, "Missing element (secret)\n");
1466
                                error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1467
                                g_snprintf(error_cause, 512, "Missing element (secret)");
1468
                                goto error;
1469
                        }
1470
                        if(!json_is_string(secret)) {
1471
                                janus_mutex_unlock(&rooms_mutex);
1472
                                JANUS_LOG(LOG_ERR, "Invalid element (secret should be a string)\n");
1473
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
1474
                                g_snprintf(error_cause, 512, "Invalid element (secret should be a string)");
1475
                                goto error;
1476
                        }
1477
                        if(!janus_strcmp_const_time(videoroom->room_secret, json_string_value(secret))) {
1478
                                janus_mutex_unlock(&rooms_mutex);
1479
                                JANUS_LOG(LOG_ERR, "Unauthorized (wrong secret)\n");
1480
                                error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
1481
                                g_snprintf(error_cause, 512, "Unauthorized (wrong secret)");
1482
                                goto error;
1483
                        }
1484
                }
1485
                janus_mutex_lock(&videoroom->participants_mutex);
1486
                janus_videoroom_participant* publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(publisher_id));
1487
                if(publisher == NULL) {
1488
                        janus_mutex_unlock(&videoroom->participants_mutex);
1489
                        JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
1490
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
1491
                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
1492
                        goto error;
1493
                }
1494
                janus_mutex_lock(&publisher->rtp_forwarders_mutex);
1495
                g_hash_table_remove(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id));
1496
                janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
1497
                janus_mutex_unlock(&videoroom->participants_mutex);
1498
                response = json_object();
1499
                json_object_set_new(response, "videoroom", json_string("stop_rtp_forward"));
1500
                json_object_set_new(response, "room", json_integer(room_id));
1501
                json_object_set_new(response, "publisher_id", json_integer(publisher_id));
1502
                json_object_set_new(response, "stream_id", json_integer(stream_id));
1503
                goto plugin_response;
1504
        } else if(!strcasecmp(request_text, "exists")) {
1505
                /* Check whether a given room exists or not, returns true/false */        
1506
                json_t *room = json_object_get(root, "room");
1507
                if(!room || !json_is_integer(room) || json_integer_value(room) < 0) {
1508
                        JANUS_LOG(LOG_ERR, "Invalid request, room number must be included in request and must be a positive integer\n");
1509
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1510
                        g_snprintf(error_cause, 512, "Missing element (room)");
1511
                        goto error;
1512
                }
1513
                guint64 room_id = json_integer_value(room);
1514
                janus_mutex_lock(&rooms_mutex);
1515
                gboolean room_exists = g_hash_table_contains(rooms, GUINT_TO_POINTER(room_id));
1516
                janus_mutex_unlock(&rooms_mutex);
1517
                response = json_object();
1518
                json_object_set_new(response, "videoroom", json_string("success"));
1519
                json_object_set_new(response, "room", json_integer(room_id));
1520
                json_object_set_new(response, "exists", json_string(room_exists ? "true" : "false"));
1521
                goto plugin_response;
1522
        } else if(!strcasecmp(request_text, "listparticipants")) {
1523
                /* List all participants in a room, specifying whether they're publishers or just attendees */        
1524
                json_t *room = json_object_get(root, "room");
1525
                if(!room || !json_is_integer(room) || json_integer_value(room) < 0) {
1526
                        JANUS_LOG(LOG_ERR, "Invalid request, room number must be included in request and must be a positive integer\n");
1527
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
1528
                        g_snprintf(error_cause, 512, "Missing element (room)");
1529
                        goto error;
1530
                }
1531
                guint64 room_id = json_integer_value(room);
1532
                janus_mutex_lock(&rooms_mutex);
1533
                janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
1534
                janus_mutex_unlock(&rooms_mutex);
1535
                if(videoroom == NULL) {
1536
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1537
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1538
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1539
                        goto error;
1540
                }
1541
                if(videoroom->destroyed) {
1542
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1543
                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
1544
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1545
                        goto error;
1546
                }
1547
                /* Return a list of all participants (whether they're publishing or not) */
1548
                json_t *list = json_array();
1549
                GHashTableIter iter;
1550
                gpointer value;
1551
                janus_mutex_lock(&videoroom->participants_mutex);
1552
                g_hash_table_iter_init(&iter, videoroom->participants);
1553
                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1554
                        janus_videoroom_participant *p = value;
1555
                        json_t *pl = json_object();
1556
                        json_object_set_new(pl, "id", json_integer(p->user_id));
1557
                        if(p->display)
1558
                                json_object_set_new(pl, "display", json_string(p->display));
1559
                        json_object_set_new(pl, "publisher", json_string(p->sdp ? "true" : "false"));
1560
                        json_array_append_new(list, pl);
1561
                }
1562
                janus_mutex_unlock(&videoroom->participants_mutex);
1563
                response = json_object();
1564
                json_object_set_new(response, "videoroom", json_string("participants"));
1565
                json_object_set_new(response, "room", json_integer(room_id));
1566
                json_object_set_new(response, "participants", list);
1567
                goto plugin_response;
1568
        } else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")
1569
                        || !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish") || !strcasecmp(request_text, "unpublish")
1570
                        || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "switch") || !strcasecmp(request_text, "stop")
1571
                        || !strcasecmp(request_text, "add") || !strcasecmp(request_text, "remove") || !strcasecmp(request_text, "leave")) {
1572
                /* These messages are handled asynchronously */
1573

    
1574
                janus_videoroom_message *msg = calloc(1, sizeof(janus_videoroom_message));
1575
                if(msg == NULL) {
1576
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
1577
                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1578
                        g_snprintf(error_cause, 512, "Memory error");
1579
                        goto error;
1580
                }
1581

    
1582
                g_free(message);
1583
                msg->handle = handle;
1584
                msg->transaction = transaction;
1585
                msg->message = root;
1586
                msg->sdp_type = sdp_type;
1587
                msg->sdp = sdp;
1588
                g_async_queue_push(messages, msg);
1589

    
1590
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL);
1591
        } else {
1592
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1593
                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
1594
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1595
                goto error;
1596
        }
1597

    
1598
plugin_response:
1599
                {
1600
                        if (!response) {
1601
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
1602
                                g_snprintf(error_cause, 512, "Invalid response");
1603
                                goto error;
1604
                        }
1605
                        if(root != NULL)
1606
                                json_decref(root);
1607
                        g_free(transaction);
1608
                        g_free(message);
1609
                        g_free(sdp_type);
1610
                        g_free(sdp);
1611

    
1612
                        char *response_text = json_dumps(response, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1613
                        json_decref(response);
1614
                        janus_plugin_result *result = janus_plugin_result_new(JANUS_PLUGIN_OK, response_text);
1615
                        g_free(response_text);
1616
                        return result;
1617
                }
1618

    
1619
error:
1620
                {
1621
                        if(root != NULL)
1622
                                json_decref(root);
1623
                        g_free(transaction);
1624
                        g_free(message);
1625
                        g_free(sdp_type);
1626
                        g_free(sdp);
1627

    
1628
                        /* Prepare JSON error event */
1629
                        json_t *event = json_object();
1630
                        json_object_set_new(event, "videoroom", json_string("event"));
1631
                        json_object_set_new(event, "error_code", json_integer(error_code));
1632
                        json_object_set_new(event, "error", json_string(error_cause));
1633
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1634
                        json_decref(event);
1635
                        janus_plugin_result *result = janus_plugin_result_new(JANUS_PLUGIN_OK, event_text);
1636
                        g_free(event_text);
1637
                        return result;
1638
                }
1639

    
1640
}
1641

    
1642
void janus_videoroom_setup_media(janus_plugin_session *handle) {
1643
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
1644
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1645
                return;
1646
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1647
        if(!session) {
1648
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1649
                return;
1650
        }
1651
        if(session->destroyed)
1652
                return;
1653
        /* Media relaying can start now */
1654
        session->started = TRUE;
1655
        /* If this is a listener, ask the publisher a FIR */
1656
        if(session->participant) {
1657
                if(session->participant_type == janus_videoroom_p_type_subscriber) {
1658
                        janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
1659
                        if(l && l->feed) {
1660
                                janus_videoroom_participant *p = l->feed;
1661
                                if(p && p->session) {
1662
                                        /* Send a FIR */
1663
                                        char buf[20];
1664
                                        memset(buf, 0, 20);
1665
                                        janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
1666
                                        JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1667
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 20);
1668
                                        /* Send a PLI too, just in case... */
1669
                                        memset(buf, 0, 12);
1670
                                        janus_rtcp_pli((char *)&buf, 12);
1671
                                        JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1672
                                        gateway->relay_rtcp(p->session->handle, 1, buf, 12);
1673
                                }
1674
                        }
1675
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1676
                        /* Do the same, but for all feeds */
1677
                        janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
1678
                        if(listener == NULL)
1679
                                return;
1680
                        GSList *ps = listener->listeners;
1681
                        while(ps) {
1682
                                janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
1683
                                if(l && l->feed) {
1684
                                        janus_videoroom_participant *p = l->feed;
1685
                                        if(p && p->session) {
1686
                                                /* Send a FIR */
1687
                                                char buf[20];
1688
                                                memset(buf, 0, 20);
1689
                                                janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
1690
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending FIR to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1691
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 20);
1692
                                                /* Send a PLI too, just in case... */
1693
                                                memset(buf, 0, 12);
1694
                                                janus_rtcp_pli((char *)&buf, 12);
1695
                                                JANUS_LOG(LOG_VERB, "New Multiplexed listener available, sending PLI to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1696
                                                gateway->relay_rtcp(p->session->handle, 1, buf, 12);
1697
                                        }
1698
                                }
1699
                                ps = ps->next;
1700
                        }
1701
                }
1702
        }
1703
}
1704

    
1705
void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
1706
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
1707
                return;
1708
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1709
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
1710
                return;
1711
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1712
        if((!video && participant->audio_active) || (video && participant->video_active)) {
1713
                /* Update payload type and SSRC */
1714
                rtp_header *rtp = (rtp_header *)buf;
1715
                rtp->type = video ? VP8_PT : OPUS_PT;
1716
                rtp->ssrc = htonl(video ? participant->video_ssrc : participant->audio_ssrc);
1717
                /* Forward RTP to the appropriate port for the rtp_forwarders associated wih this publisher, if there are any */
1718
                GHashTableIter iter;
1719
                gpointer value;
1720
                g_hash_table_iter_init(&iter, participant->rtp_forwarders);
1721
                janus_mutex_lock(&participant->rtp_forwarders_mutex);
1722
                while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
1723
                        rtp_forwarder* rtp_forward = (rtp_forwarder*)value;
1724
                        if(video && rtp_forward->is_video) {
1725
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
1726
                        }
1727
                        else if(!video && !rtp_forward->is_video) {
1728
                                sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr));
1729
                        }
1730
                }
1731
                janus_mutex_unlock(&participant->rtp_forwarders_mutex);
1732
                /* Save the frame if we're recording */
1733
                if(video && participant->vrc)
1734
                        janus_recorder_save_frame(participant->vrc, buf, len);
1735
                else if(!video && participant->arc)
1736
                        janus_recorder_save_frame(participant->arc, buf, len);
1737
                /* Done, relay it */
1738
                janus_videoroom_rtp_relay_packet packet;
1739
                packet.data = rtp;
1740
                packet.length = len;
1741
                packet.is_video = video;
1742
                /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */
1743
                packet.timestamp = ntohl(packet.data->timestamp);
1744
                packet.seq_number = ntohs(packet.data->seq_number);
1745
                /* Go */
1746
                g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
1747
                
1748
                /* Check if we need to send any REMB, FIR or PLI back to this publisher */
1749
                if(video && participant->video_active) {
1750
                        /* Did we send a REMB already, or is it time to send one? */
1751
                        gboolean send_remb = FALSE;
1752
                        if(participant->remb_latest == 0 && participant->remb_startup > 0) {
1753
                                /* Still in the starting phase, send the ramp-up REMB feedback */
1754
                                send_remb = TRUE;
1755
                        } else if(participant->remb_latest > 0 && janus_get_monotonic_time()-participant->remb_latest >= 5*G_USEC_PER_SEC) {
1756
                                /* 5 seconds have passed since the last REMB, send a new one */
1757
                                send_remb = TRUE;
1758
                        }                
1759
                        if(send_remb) {
1760
                                /* We send a few incremental REMB messages at startup */
1761
                                uint64_t bitrate = (participant->bitrate ? participant->bitrate : 256*1024);
1762
                                if(participant->remb_startup > 0) {
1763
                                        bitrate = bitrate/participant->remb_startup;
1764
                                        participant->remb_startup--;
1765
                                }
1766
                                char rtcpbuf[200];
1767
                                memset(rtcpbuf, 0, 200);
1768
                                /* FIXME First put a RR (fake)... */
1769
                                int rrlen = 32;
1770
                                rtcp_rr *rr = (rtcp_rr *)&rtcpbuf;
1771
                                rr->header.version = 2;
1772
                                rr->header.type = RTCP_RR;
1773
                                rr->header.rc = 1;
1774
                                rr->header.length = htons((rrlen/4)-1);
1775
                                /* ... then put a SDES... */
1776
                                int sdeslen = janus_rtcp_sdes((char *)(&rtcpbuf)+rrlen, 200-rrlen, "janusvideo", 10);
1777
                                if(sdeslen > 0) {
1778
                                        /* ... and then finally a REMB */
1779
                                        janus_rtcp_remb((char *)(&rtcpbuf)+rrlen+sdeslen, 24, bitrate);
1780
                                        gateway->relay_rtcp(handle, video, rtcpbuf, rrlen+sdeslen+24);
1781
                                }
1782
                                JANUS_LOG(LOG_VERB, "Sending REMB\n");
1783
                                if(participant->remb_startup == 0)
1784
                                        participant->remb_latest = janus_get_monotonic_time();
1785
                        }
1786
                        /* Generate FIR/PLI too, if needed */
1787
                        if(video && participant->video_active && (participant->room->fir_freq > 0)) {
1788
                                /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
1789
                                gint64 now = janus_get_monotonic_time();
1790
                                if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
1791
                                        /* FIXME We send a FIR every tot seconds */
1792
                                        participant->fir_latest = now;
1793
                                        char rtcpbuf[24];
1794
                                        memset(rtcpbuf, 0, 24);
1795
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &participant->fir_seq);
1796
                                        JANUS_LOG(LOG_VERB, "Sending FIR to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
1797
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 20);
1798
                                        /* Send a PLI too, just in case... */
1799
                                        memset(rtcpbuf, 0, 12);
1800
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
1801
                                        JANUS_LOG(LOG_VERB, "Sending PLI to %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
1802
                                        gateway->relay_rtcp(handle, video, rtcpbuf, 12);
1803
                                }
1804
                        }
1805
                }
1806
        }
1807
}
1808

    
1809
void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
1810
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1811
                return;
1812
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1813
        if(!session) {
1814
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1815
                return;
1816
        }
1817
        if(session->destroyed)
1818
                return;
1819
        if(session->participant_type == janus_videoroom_p_type_subscriber) {
1820
                /* A listener sent some RTCP, check what it is and if we need to forward it to the publisher */
1821
                janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
1822
                if(!l->video)
1823
                        return;        /* The only feedback we handle is video related anyway... */
1824
                if(janus_rtcp_has_fir(buf, len)) {
1825
                        /* We got a FIR, forward it to the publisher */
1826
                        if(l && l->feed) {
1827
                                janus_videoroom_participant *p = l->feed;
1828
                                if(p && p->session) {
1829
                                        char rtcpbuf[20];
1830
                                        memset(rtcpbuf, 0, 20);
1831
                                        janus_rtcp_fir((char *)&rtcpbuf, 20, &p->fir_seq);
1832
                                        JANUS_LOG(LOG_VERB, "Got a FIR from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1833
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 20);
1834
                                }
1835
                        }
1836
                }
1837
                if(janus_rtcp_has_pli(buf, len)) {
1838
                        /* We got a PLI, forward it to the publisher */
1839
                        if(l && l->feed) {
1840
                                janus_videoroom_participant *p = l->feed;
1841
                                if(p && p->session) {
1842
                                        char rtcpbuf[12];
1843
                                        memset(rtcpbuf, 0, 12);
1844
                                        janus_rtcp_pli((char *)&rtcpbuf, 12);
1845
                                        JANUS_LOG(LOG_VERB, "Got a PLI from a listener, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1846
                                        gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 12);
1847
                                }
1848
                        }
1849
                }
1850
                uint64_t bitrate = janus_rtcp_get_remb(buf, len);
1851
                if(bitrate > 0) {
1852
                        /* FIXME We got a REMB from this listener, should we do something about it? */
1853
                }
1854
        }
1855
}
1856

    
1857
void janus_videoroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
1858
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
1859
                return;
1860
        if(buf == NULL || len <= 0)
1861
                return;
1862
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1863
        if(!session || session->destroyed || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
1864
                return;
1865
        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1866
        janus_videoroom_data_relay_packet packet;
1867
        packet.data = buf;
1868
        packet.length = len;
1869
        g_slist_foreach(participant->listeners, janus_videoroom_relay_data_packet, &packet);
1870
}
1871

    
1872
void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
1873
        /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */
1874
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
1875
                return;
1876
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
1877
        if(!session || session->destroyed || !session->participant)
1878
                return;
1879
        /* Check if it's an uplink (publisher) or downlink (viewer) issue */
1880
        if(session->participant_type == janus_videoroom_p_type_publisher) {
1881
                if(!uplink) {
1882
                        janus_videoroom_participant *publisher = (janus_videoroom_participant *)session->participant;
1883
                        if(publisher) {
1884
                                /* Send an event on the handle to notify the application: it's
1885
                                 * up to the application to then choose a policy and enforce it */
1886
                                json_t *event = json_object();
1887
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
1888
                                /* Also add info on what the current bitrate cap is */
1889
                                uint64_t bitrate = (publisher->bitrate ? publisher->bitrate : 256*1024);
1890
                                json_object_set_new(event, "current-bitrate", json_integer(bitrate));
1891
                                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1892
                                json_decref(event);
1893
                                event = NULL;
1894
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event_text, NULL, NULL);
1895
                                g_free(event_text);
1896
                                event_text = NULL;
1897
                        }
1898
                } else {
1899
                        JANUS_LOG(LOG_WARN, "Got a slow uplink on a VideoRoom publisher? Weird, because it doesn't receive media...\n");
1900
                }
1901
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1902
                if(uplink) {
1903
                        janus_videoroom_listener *viewer = (janus_videoroom_listener *)session->participant;
1904
                        if(viewer) {
1905
                                /* Send an event on the handle to notify the application: it's
1906
                                 * up to the application to then choose a policy and enforce it */
1907
                                json_t *event = json_object();
1908
                                json_object_set_new(event, "videoroom", json_string("slow_link"));
1909
                                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1910
                                json_decref(event);
1911
                                event = NULL;
1912
                                gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event_text, NULL, NULL);
1913
                                g_free(event_text);
1914
                                event_text = NULL;
1915
                        }
1916
                } else {
1917
                        JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
1918
                }
1919
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
1920
                /* TBD. */
1921
        }
1922
}
1923

    
1924
void janus_videoroom_hangup_media(janus_plugin_session *handle) {
1925
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
1926
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1927
                return;
1928
        janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;        
1929
        if(!session) {
1930
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1931
                return;
1932
        }
1933
        session->started = FALSE;
1934
        if(session->destroyed || session->hangingup)
1935
                return;
1936
        session->hangingup = TRUE;
1937
        /* Send an event to the browser and tell the PeerConnection is over */
1938
        if(session->participant_type == janus_videoroom_p_type_publisher) {
1939
                /* This publisher just 'unpublished' */
1940
                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
1941
                if(participant->sdp)
1942
                        g_free(participant->sdp);
1943
                participant->sdp = NULL;
1944
                participant->firefox = FALSE;
1945
                participant->audio_active = FALSE;
1946
                participant->video_active = FALSE;
1947
                participant->remb_startup = 4;
1948
                participant->remb_latest = 0;
1949
                participant->fir_latest = 0;
1950
                participant->fir_seq = 0;
1951
                /* Get rid of the recorders, if available */
1952
                if(participant->arc) {
1953
                        janus_recorder_close(participant->arc);
1954
                        JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
1955
                        janus_recorder_free(participant->arc);
1956
                }
1957
                participant->arc = NULL;
1958
                if(participant->vrc) {
1959
                        janus_recorder_close(participant->vrc);
1960
                        JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
1961
                        janus_recorder_free(participant->vrc);
1962
                }
1963
                participant->vrc = NULL;
1964
                janus_mutex_lock(&participant->listeners_mutex);
1965
                while(participant->listeners) {
1966
                        janus_videoroom_listener *l = (janus_videoroom_listener *)participant->listeners->data;
1967
                        if(l) {
1968
                                participant->listeners = g_slist_remove(participant->listeners, l);
1969
                                l->feed = NULL;
1970
                        }
1971
                }
1972
                janus_mutex_unlock(&participant->listeners_mutex);
1973
                json_t *event = json_object();
1974
                json_object_set_new(event, "videoroom", json_string("event"));
1975
                json_object_set_new(event, "room", json_integer(participant->room->room_id));
1976
                json_object_set_new(event, "unpublished", json_integer(participant->user_id));
1977
                char *unpub_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1978
                json_decref(event);
1979
                GHashTableIter iter;
1980
                gpointer value;
1981
                if(participant && participant->room) {
1982
                        if(!participant->room->destroyed) {
1983
                                janus_mutex_lock(&participant->room->participants_mutex);
1984
                                g_hash_table_iter_init(&iter, participant->room->participants);
1985
                                while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
1986
                                        janus_videoroom_participant *p = value;
1987
                                        if(p && p->session && p != participant) {
1988
                                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
1989
                                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, unpub_text, NULL, NULL);
1990
                                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1991
                                        }
1992
                                }
1993
                                janus_mutex_unlock(&participant->room->participants_mutex);
1994
                        }
1995
                }
1996
                g_free(unpub_text);
1997
        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
1998
                /* Get rid of listener */
1999
                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2000
                if(listener) {
2001
                        listener->paused = TRUE;
2002
                        janus_videoroom_participant *publisher = listener->feed;
2003
                        if(publisher != NULL) {
2004
                                janus_mutex_lock(&publisher->listeners_mutex);
2005
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
2006
                                janus_mutex_unlock(&publisher->listeners_mutex);
2007
                                listener->feed = NULL;
2008
                        }
2009
                }
2010
                /* TODO Should we close the handle as well? */
2011
        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2012
                /* Do the same, but for all sub-listener */
2013
                janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
2014
                GSList *ps = listener->listeners;
2015
                while(ps) {
2016
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
2017
                        if(l) {
2018
                                l->paused = TRUE;
2019
                                janus_videoroom_participant *publisher = l->feed;
2020
                                if(publisher != NULL) {
2021
                                        janus_mutex_lock(&publisher->listeners_mutex);
2022
                                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
2023
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2024
                                        l->feed = NULL;
2025
                                }
2026
                        }
2027
                        /* TODO Should we close the handle as well? */
2028
                        ps = ps->next;
2029
                }
2030
                /* TODO Should we close the handle as well? */
2031
        }
2032
        /* Done */
2033
        session->hangingup = FALSE;
2034
}
2035

    
2036
/* Thread to handle incoming messages */
2037
static void *janus_videoroom_handler(void *data) {
2038
        JANUS_LOG(LOG_VERB, "Joining VideoRoom handler thread\n");
2039
        janus_videoroom_message *msg = NULL;
2040
        int error_code = 0;
2041
        char *error_cause = calloc(512, sizeof(char));        /* FIXME 512 should be enough, but anyway... */
2042
        if(error_cause == NULL) {
2043
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
2044
                return NULL;
2045
        }
2046
        json_t *root = NULL;
2047
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
2048
                if(!messages || (msg = g_async_queue_try_pop(messages)) == NULL) {
2049
                        usleep(50000);
2050
                        continue;
2051
                }
2052

    
2053
                janus_videoroom_session *session = (janus_videoroom_session *)msg->handle->plugin_handle;        
2054
                if(!session) {
2055
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2056
                        janus_videoroom_message_free(msg);
2057
                        continue;
2058
                }
2059
                if(session->destroyed) {
2060
                        janus_videoroom_message_free(msg);
2061
                        continue;
2062
                }
2063
                /* Handle request */
2064
                error_code = 0;
2065
                root = NULL;
2066
                if(msg->message == NULL) {
2067
                        JANUS_LOG(LOG_ERR, "No message??\n");
2068
                        error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
2069
                        g_snprintf(error_cause, 512, "%s", "No message??");
2070
                        goto error;
2071
                }
2072
                root = msg->message;
2073
                /* Get the request first */
2074
                json_t *request = json_object_get(root, "request");
2075
                if(!request) {
2076
                        JANUS_LOG(LOG_ERR, "Missing element (request)\n");
2077
                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
2078
                        g_snprintf(error_cause, 512, "Missing element (request)");
2079
                        goto error;
2080
                }
2081
                if(!json_is_string(request)) {
2082
                        JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n");
2083
                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2084
                        g_snprintf(error_cause, 512, "Invalid element (request should be a string)");
2085
                        goto error;
2086
                }
2087
                const char *request_text = json_string_value(request);
2088
                json_t *event = NULL;
2089
                /* 'create' and 'destroy' are handled synchronously: what kind of participant is this session referring to? */
2090
                if(session->participant_type == janus_videoroom_p_type_none) {
2091
                        JANUS_LOG(LOG_VERB, "Configuring new participant\n");
2092
                        /* Not configured yet, we need to do this now */
2093
                        if(strcasecmp(request_text, "join") && strcasecmp(request_text, "joinandconfigure")) {
2094
                                JANUS_LOG(LOG_ERR, "Invalid request on unconfigured participant\n");
2095
                                error_code = JANUS_VIDEOROOM_ERROR_JOIN_FIRST;
2096
                                g_snprintf(error_cause, 512, "Invalid request on unconfigured participant");
2097
                                goto error;
2098
                        }
2099
                        json_t *room = json_object_get(root, "room");
2100
                        if(!room) {
2101
                                JANUS_LOG(LOG_ERR, "Missing element (room)\n");
2102
                                g_snprintf(error_cause, 512, "Missing element (room)");
2103
                                goto error;
2104
                        }
2105
                        if(!json_is_integer(room) || json_integer_value(room) < 0) {
2106
                                JANUS_LOG(LOG_ERR, "Invalid element (room should be a positive integer)\n");
2107
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2108
                                g_snprintf(error_cause, 512, "Invalid element (room should be a positive integer)");
2109
                                goto error;
2110
                        }
2111
                        guint64 room_id = json_integer_value(room);
2112
                        janus_mutex_lock(&rooms_mutex);
2113
                        janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
2114
                        janus_mutex_unlock(&rooms_mutex);
2115
                        if(videoroom == NULL) {
2116
                                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2117
                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2118
                                g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2119
                                goto error;
2120
                        }
2121
                        if(videoroom->destroyed) {
2122
                                JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2123
                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2124
                                g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
2125
                                goto error;
2126
                        }
2127
                        json_t *ptype = json_object_get(root, "ptype");
2128
                        if(!ptype) {
2129
                                JANUS_LOG(LOG_ERR, "Missing element (ptype)\n");
2130
                                error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
2131
                                g_snprintf(error_cause, 512, "Missing element (ptype)");
2132
                                goto error;
2133
                        }
2134
                        if(!json_is_string(ptype)) {
2135
                                JANUS_LOG(LOG_ERR, "Invalid element (ptype should be a string)\n");
2136
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2137
                                g_snprintf(error_cause, 512, "Invalid element (ptype should be a string)");
2138
                                goto error;
2139
                        }
2140
                        const char *ptype_text = json_string_value(ptype);
2141
                        if(!strcasecmp(ptype_text, "publisher")) {
2142
                                JANUS_LOG(LOG_VERB, "Configuring new publisher\n");
2143
                                json_t *display = json_object_get(root, "display");
2144
                                if(display && !json_is_string(display)) {
2145
                                        JANUS_LOG(LOG_ERR, "Invalid element (display should be a string)\n");
2146
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2147
                                        g_snprintf(error_cause, 512, "Invalid element (display should be a string)");
2148
                                        goto error;
2149
                                }
2150
                                const char *display_text = display ? json_string_value(display) : NULL;
2151
                                guint64 user_id = 0;
2152
                                json_t *id = json_object_get(root, "id");
2153
                                if(id) {
2154
                                        if(!json_is_integer(id) || json_integer_value(id) < 0) {
2155
                                                JANUS_LOG(LOG_ERR, "Invalid element (id should be a positive integer)\n");
2156
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2157
                                                g_snprintf(error_cause, 512, "Invalid element (id should be a positive integer)");
2158
                                                goto error;
2159
                                        }
2160
                                        user_id = json_integer_value(id);
2161
                                        janus_mutex_lock(&videoroom->participants_mutex);
2162
                                        if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
2163
                                                janus_mutex_unlock(&videoroom->participants_mutex);
2164
                                                /* User ID already taken */
2165
                                                JANUS_LOG(LOG_ERR, "User ID %"SCNu64" already exists\n", user_id);
2166
                                                error_code = JANUS_VIDEOROOM_ERROR_ID_EXISTS;
2167
                                                g_snprintf(error_cause, 512, "User ID %"SCNu64" already exists", user_id);
2168
                                                goto error;
2169
                                        }
2170
                                        janus_mutex_unlock(&videoroom->participants_mutex);
2171
                                }
2172
                                if(user_id == 0) {
2173
                                        /* Generate a random ID */
2174
                                        janus_mutex_lock(&videoroom->participants_mutex);
2175
                                        while(user_id == 0) {
2176
                                                user_id = g_random_int();
2177
                                                if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
2178
                                                        /* User ID already taken, try another one */
2179
                                                        user_id = 0;
2180
                                                }
2181
                                        }
2182
                                        janus_mutex_unlock(&videoroom->participants_mutex);
2183
                                }
2184
                                JANUS_LOG(LOG_VERB, "  -- Publisher ID: %"SCNu64"\n", user_id);
2185
                                json_t *audio = NULL, *video = NULL, *bitrate = NULL, *record = NULL, *recfile = NULL;
2186
                                if(!strcasecmp(request_text, "joinandconfigure")) {
2187
                                        /* Also configure (or publish a new feed) audio/video/bitrate for this new publisher */
2188
                                        audio = json_object_get(root, "audio");
2189
                                        if(audio && !json_is_boolean(audio)) {
2190
                                                JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
2191
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2192
                                                g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
2193
                                                goto error;
2194
                                        }
2195
                                        video = json_object_get(root, "video");
2196
                                        if(video && !json_is_boolean(video)) {
2197
                                                JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
2198
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2199
                                                g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
2200
                                                goto error;
2201
                                        }
2202
                                        bitrate = json_object_get(root, "bitrate");
2203
                                        if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) {
2204
                                                JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n");
2205
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2206
                                                g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)");
2207
                                                goto error;
2208
                                        }
2209
                                        record = json_object_get(root, "record");
2210
                                        if(record && !json_is_boolean(record)) {
2211
                                                JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n");
2212
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2213
                                                g_snprintf(error_cause, 512, "Invalid value (record should be a boolean)");
2214
                                                goto error;
2215
                                        }
2216
                                        recfile = json_object_get(root, "filename");
2217
                                        if(recfile && !json_is_string(recfile)) {
2218
                                                JANUS_LOG(LOG_ERR, "Invalid element (filename should be a string)\n");
2219
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2220
                                                g_snprintf(error_cause, 512, "Invalid value (filename should be a string)");
2221
                                                goto error;
2222
                                        }
2223
                                }
2224
                                janus_videoroom_participant *publisher = calloc(1, sizeof(janus_videoroom_participant));
2225
                                if(publisher == NULL) {
2226
                                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
2227
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2228
                                        g_snprintf(error_cause, 512, "Memory error");
2229
                                        goto error;
2230
                                }
2231
                                publisher->session = session;
2232
                                publisher->room = videoroom;
2233
                                publisher->user_id = user_id;
2234
                                publisher->display = display_text ? g_strdup(display_text) : NULL;
2235
                                publisher->sdp = NULL;                /* We'll deal with this later */
2236
                                publisher->audio = FALSE;        /* We'll deal with this later */
2237
                                publisher->video = FALSE;        /* We'll deal with this later */
2238
                                publisher->data = FALSE;        /* We'll deal with this later */
2239
                                publisher->audio_active = FALSE;
2240
                                publisher->video_active = FALSE;
2241
                                publisher->recording_active = FALSE;
2242
                                publisher->recording_base = NULL;
2243
                                publisher->arc = NULL;
2244
                                publisher->vrc = NULL;
2245
                                publisher->firefox = FALSE;
2246
                                publisher->bitrate = videoroom->bitrate;
2247
                                publisher->listeners = NULL;
2248
                                janus_mutex_init(&publisher->listeners_mutex);
2249
                                publisher->audio_ssrc = g_random_int();
2250
                                publisher->video_ssrc = g_random_int();
2251
                                publisher->remb_startup = 4;
2252
                                publisher->remb_latest = 0;
2253
                                publisher->fir_latest = 0;
2254
                                publisher->fir_seq = 0;
2255
                                janus_mutex_init(&publisher->rtp_forwarders_mutex);
2256
                                publisher->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_rtp_forwarder_free_helper);
2257
                                publisher->udp_sock = -1;
2258
                                /* In case we also wanted to configure */
2259
                                if(audio) {
2260
                                        publisher->audio_active = json_is_true(audio);
2261
                                        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);
2262
                                }
2263
                                if(video) {
2264
                                        publisher->video_active = json_is_true(video);
2265
                                        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);
2266
                                }
2267
                                if(bitrate) {
2268
                                        publisher->bitrate = json_integer_value(bitrate);
2269
                                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", publisher->bitrate, publisher->room->room_id, publisher->user_id);
2270
                                }
2271
                                if(record) {
2272
                                        publisher->recording_active = json_is_true(record);
2273
                                        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);
2274
                                }
2275
                                if(recfile) {
2276
                                        publisher->recording_base = g_strdup(json_string_value(recfile));
2277
                                        JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", publisher->recording_base, publisher->room->room_id, publisher->user_id);
2278
                                }
2279
                                /* Done */
2280
                                session->participant_type = janus_videoroom_p_type_publisher;
2281
                                session->participant = publisher;
2282
                                /* Return a list of all available publishers (those with an SDP available, that is) */
2283
                                json_t *list = json_array();
2284
                                GHashTableIter iter;
2285
                                gpointer value;
2286
                                janus_mutex_lock(&videoroom->participants_mutex);
2287
                                g_hash_table_insert(videoroom->participants, GUINT_TO_POINTER(user_id), publisher);
2288
                                g_hash_table_iter_init(&iter, videoroom->participants);
2289
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2290
                                        janus_videoroom_participant *p = value;
2291
                                        if(p == publisher || !p->sdp) {
2292
                                                continue;
2293
                                        }
2294
                                        json_t *pl = json_object();
2295
                                        json_object_set_new(pl, "id", json_integer(p->user_id));
2296
                                        if(p->display)
2297
                                                json_object_set_new(pl, "display", json_string(p->display));
2298
                                        json_array_append_new(list, pl);
2299
                                }
2300
                                janus_mutex_unlock(&videoroom->participants_mutex);
2301
                                event = json_object();
2302
                                json_object_set_new(event, "videoroom", json_string("joined"));
2303
                                json_object_set_new(event, "room", json_integer(videoroom->room_id));
2304
                                json_object_set_new(event, "description", json_string(videoroom->room_name));
2305
                                json_object_set_new(event, "id", json_integer(user_id));
2306
                                json_object_set_new(event, "publishers", list);
2307
                        } else if(!strcasecmp(ptype_text, "listener")) {
2308
                                JANUS_LOG(LOG_VERB, "Configuring new listener\n");
2309
                                /* This is a new listener */
2310
                                json_t *feed = json_object_get(root, "feed");
2311
                                if(!feed) {
2312
                                        JANUS_LOG(LOG_ERR, "Missing element (feed)\n");
2313
                                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
2314
                                        g_snprintf(error_cause, 512, "Missing element (feed)");
2315
                                        goto error;
2316
                                }
2317
                                if(!json_is_integer(feed) || json_integer_value(feed) < 0) {
2318
                                        JANUS_LOG(LOG_ERR, "Invalid element (feed should be a positive integer)\n");
2319
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2320
                                        g_snprintf(error_cause, 512, "Invalid element (feed should be a positive integer)");
2321
                                        goto error;
2322
                                }
2323
                                guint64 feed_id = json_integer_value(feed);
2324
                                json_t *audio = json_object_get(root, "audio");
2325
                                if(audio && !json_is_boolean(audio)) {
2326
                                        JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
2327
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2328
                                        g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
2329
                                        goto error;
2330
                                }
2331
                                json_t *video = json_object_get(root, "video");
2332
                                if(video && !json_is_boolean(video)) {
2333
                                        JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
2334
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2335
                                        g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
2336
                                        goto error;
2337
                                }
2338
                                json_t *data = json_object_get(root, "data");
2339
                                if(data && !json_is_boolean(data)) {
2340
                                        JANUS_LOG(LOG_ERR, "Invalid element (data should be a boolean)\n");
2341
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2342
                                        g_snprintf(error_cause, 512, "Invalid value (data should be a boolean)");
2343
                                        goto error;
2344
                                }
2345
                                janus_mutex_lock(&videoroom->participants_mutex);
2346
                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
2347
                                janus_mutex_unlock(&videoroom->participants_mutex);
2348
                                if(publisher == NULL || publisher->sdp == NULL) {
2349
                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
2350
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2351
                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
2352
                                        goto error;
2353
                                } else {
2354
                                        janus_videoroom_listener *listener = calloc(1, sizeof(janus_videoroom_listener));
2355
                                        if(listener == NULL) {
2356
                                                JANUS_LOG(LOG_FATAL, "Memory error!\n");
2357
                                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2358
                                                g_snprintf(error_cause, 512, "Memory error");
2359
                                                goto error;
2360
                                        }
2361
                                        listener->session = session;
2362
                                        listener->room = videoroom;
2363
                                        listener->feed = publisher;
2364
                                        /* Initialize the listener context */
2365
                                        listener->context.a_last_ssrc = 0;
2366
                                        listener->context.a_last_ssrc = 0;
2367
                                        listener->context.a_last_ts = 0;
2368
                                        listener->context.a_base_ts = 0;
2369
                                        listener->context.a_base_ts_prev = 0;
2370
                                        listener->context.v_last_ssrc = 0;
2371
                                        listener->context.v_last_ts = 0;
2372
                                        listener->context.v_base_ts = 0;
2373
                                        listener->context.v_base_ts_prev = 0;
2374
                                        listener->context.a_last_seq = 0;
2375
                                        listener->context.a_base_seq = 0;
2376
                                        listener->context.a_base_seq_prev = 0;
2377
                                        listener->context.v_last_seq = 0;
2378
                                        listener->context.v_base_seq = 0;
2379
                                        listener->context.v_base_seq_prev = 0;
2380
                                        listener->audio = audio ? json_is_true(audio) : TRUE;        /* True by default */
2381
                                        if(!publisher->audio)
2382
                                                listener->audio = FALSE;        /* ... unless the publisher isn't sending any audio */
2383
                                        listener->video = video ? json_is_true(video) : TRUE;        /* True by default */
2384
                                        if(!publisher->video)
2385
                                                listener->video = FALSE;        /* ... unless the publisher isn't sending any video */
2386
                                        listener->data = data ? json_is_true(data) : TRUE;        /* True by default */
2387
                                        if(!publisher->data)
2388
                                                listener->data = FALSE;        /* ... unless the publisher isn't sending any data */
2389
                                        listener->paused = TRUE;        /* We need an explicit start from the listener */
2390
                                        listener->parent = NULL;
2391
                                        session->participant = listener;
2392
                                        janus_mutex_lock(&publisher->listeners_mutex);
2393
                                        publisher->listeners = g_slist_append(publisher->listeners, listener);
2394
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2395
                                        event = json_object();
2396
                                        json_object_set_new(event, "videoroom", json_string("attached"));
2397
                                        json_object_set_new(event, "room", json_integer(videoroom->room_id));
2398
                                        json_object_set_new(event, "id", json_integer(feed_id));
2399
                                        if(publisher->display)
2400
                                                json_object_set_new(event, "display", json_string(publisher->display));
2401
                                        session->participant_type = janus_videoroom_p_type_subscriber;
2402
                                        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
2403
                                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2404
                                        json_decref(event);
2405
                                        /* Negotiate by sending the selected publisher SDP back */
2406
                                        if(publisher->sdp != NULL) {
2407
                                                /* How long will the gateway take to push the event? */
2408
                                                gint64 start = janus_get_monotonic_time();
2409
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, "offer", publisher->sdp);
2410
                                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
2411
                                                JANUS_LOG(LOG_VERB, "  >> %d\n", res);
2412
                                                g_free(event_text);
2413
                                                root = NULL;
2414
                                                janus_videoroom_message_free(msg);
2415
                                                continue;
2416
                                        }
2417
                                        g_free(event_text);
2418
                                }
2419
                        } else if(!strcasecmp(ptype_text, "muxed-listener")) {
2420
                                /* This is a new Multiplexed listener */
2421
                                JANUS_LOG(LOG_INFO, "Configuring new Multiplexed listener\n");
2422
                                /* Any feed we want to attach to already? */
2423
                                GList *list = NULL;
2424
                                json_t *feeds = json_object_get(root, "feeds");
2425
                                if(feeds && !json_is_array(feeds)) {
2426
                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds should be an array)\n");
2427
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2428
                                        g_snprintf(error_cause, 512, "Invalid element (feeds should be an array)");
2429
                                        goto error;
2430
                                }
2431
                                if(feeds && json_array_size(feeds) > 0) {
2432
                                        unsigned int i = 0;
2433
                                        int problem = 0;
2434
                                        for(i=0; i<json_array_size(feeds); i++) {
2435
                                                if(videoroom->destroyed) {
2436
                                                        problem = 1;
2437
                                                        JANUS_LOG(LOG_ERR, "Room destroyed");
2438
                                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2439
                                                        g_snprintf(error_cause, 512, "Room destroyed");
2440
                                                        break;
2441
                                                }
2442
                                                json_t *feed = json_array_get(feeds, i);
2443
                                                if(!feed || !json_is_integer(feed)) {
2444
                                                        problem = 1;
2445
                                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
2446
                                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2447
                                                        g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
2448
                                                        break;
2449
                                                }
2450
                                                uint64_t feed_id = json_integer_value(feed);
2451
                                                janus_mutex_lock(&videoroom->participants_mutex);
2452
                                                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
2453
                                                janus_mutex_unlock(&videoroom->participants_mutex);
2454
                                                if(publisher == NULL) { //~ || publisher->sdp == NULL) {
2455
                                                        /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
2456
                                                        problem = 1;
2457
                                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
2458
                                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2459
                                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
2460
                                                        break;
2461
                                                }
2462
                                                list = g_list_prepend(list, GUINT_TO_POINTER(feed_id));
2463
                                                JANUS_LOG(LOG_INFO, "  -- Subscribing to feed %"SCNu64"\n", feed_id);
2464
                                        }
2465
                                        if(problem) {
2466
                                                goto error;
2467
                                        }
2468
                                }
2469
                                /* Allocate listener */
2470
                                janus_videoroom_listener_muxed *listener = calloc(1, sizeof(janus_videoroom_listener_muxed));
2471
                                if(listener == NULL) {
2472
                                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
2473
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2474
                                        g_snprintf(error_cause, 512, "Memory error");
2475
                                        goto error;
2476
                                }
2477
                                listener->session = session;
2478
                                listener->room = videoroom;
2479
                                session->participant_type = janus_videoroom_p_type_subscriber_muxed;
2480
                                session->participant = listener;
2481
                                /* Ack that we created the listener */
2482
                                event = json_object();
2483
                                json_object_set_new(event, "videoroom", json_string("muxed-created"));
2484
                                json_object_set_new(event, "room", json_integer(videoroom->room_id));
2485
                                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
2486
                                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2487
                                json_decref(event);
2488
                                /* How long will the gateway take to push the event? */
2489
                                gint64 start = janus_get_monotonic_time();
2490
                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
2491
                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
2492
                                JANUS_LOG(LOG_VERB, "  >> %d\n", res);
2493
                                g_free(event_text);
2494
                                root = NULL;
2495
                                /* Attach to feeds if needed */
2496
                                if(list != NULL) {
2497
                                        JANUS_LOG(LOG_INFO, "Subscribing to %d feeds\n", g_list_length(list));
2498
                                        list = g_list_reverse(list);
2499
                                        if(videoroom->destroyed || janus_videoroom_muxed_subscribe(listener, list, msg->transaction) < 0) {
2500
                                                JANUS_LOG(LOG_ERR, "Error subscribing!\n");
2501
                                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
2502
                                                g_snprintf(error_cause, 512, "Error subscribing!");
2503
                                                goto error;
2504
                                        }
2505
                                }
2506
                                janus_videoroom_message_free(msg);
2507
                                continue;
2508
                        } else {
2509
                                JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
2510
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2511
                                g_snprintf(error_cause, 512, "Invalid element (ptype)");
2512
                                goto error;
2513
                        }
2514
                } else if(session->participant_type == janus_videoroom_p_type_publisher) {
2515
                        /* Handle this publisher */
2516
                        janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
2517
                        if(participant == NULL) {
2518
                                JANUS_LOG(LOG_ERR, "Invalid participant instance\n");
2519
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2520
                                g_snprintf(error_cause, 512, "Invalid participant instance");
2521
                                goto error;
2522
                        }
2523
                        if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")) {
2524
                                JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
2525
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
2526
                                g_snprintf(error_cause, 512, "Already in as a publisher on this handle");
2527
                                goto error;
2528
                        } else if(!strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish")) {
2529
                                if(!strcasecmp(request_text, "publish") && participant->sdp) {
2530
                                        JANUS_LOG(LOG_ERR, "Can't publish, already published\n");
2531
                                        error_code = JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED;
2532
                                        g_snprintf(error_cause, 512, "Can't publish, already published");
2533
                                        goto error;
2534
                                }
2535
                                /* Configure (or publish a new feed) audio/video/bitrate for this publisher */
2536
                                json_t *audio = json_object_get(root, "audio");
2537
                                if(audio && !json_is_boolean(audio)) {
2538
                                        JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
2539
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2540
                                        g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
2541
                                        goto error;
2542
                                }
2543
                                json_t *video = json_object_get(root, "video");
2544
                                if(video && !json_is_boolean(video)) {
2545
                                        JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
2546
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2547
                                        g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
2548
                                        goto error;
2549
                                }
2550
                                json_t *bitrate = json_object_get(root, "bitrate");
2551
                                if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) {
2552
                                        JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n");
2553
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2554
                                        g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)");
2555
                                        goto error;
2556
                                }
2557
                                json_t *record = json_object_get(root, "record");
2558
                                if(record && !json_is_boolean(record)) {
2559
                                        JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n");
2560
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2561
                                        g_snprintf(error_cause, 512, "Invalid value (record should be a boolean)");
2562
                                        goto error;
2563
                                }
2564
                                json_t *recfile = json_object_get(root, "filename");
2565
                                if(recfile && !json_is_string(recfile)) {
2566
                                        JANUS_LOG(LOG_ERR, "Invalid element (filename should be a string)\n");
2567
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2568
                                        g_snprintf(error_cause, 512, "Invalid value (filename should be a string)");
2569
                                        goto error;
2570
                                }
2571
                                if(audio) {
2572
                                        participant->audio_active = json_is_true(audio);
2573
                                        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);
2574
                                }
2575
                                if(video) {
2576
                                        participant->video_active = json_is_true(video);
2577
                                        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);
2578
                                }
2579
                                if(bitrate) {
2580
                                        participant->bitrate = json_integer_value(bitrate);
2581
                                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
2582
                                        /* Send a new REMB */
2583
                                        participant->remb_latest = janus_get_monotonic_time();
2584
                                        char rtcpbuf[200];
2585
                                        memset(rtcpbuf, 0, 200);
2586
                                        /* FIXME First put a RR (fake)... */
2587
                                        int rrlen = 32;
2588
                                        rtcp_rr *rr = (rtcp_rr *)&rtcpbuf;
2589
                                        rr->header.version = 2;
2590
                                        rr->header.type = RTCP_RR;
2591
                                        rr->header.rc = 1;
2592
                                        rr->header.length = htons((rrlen/4)-1);
2593
                                        /* ... then put a SDES... */
2594
                                        int sdeslen = janus_rtcp_sdes((char *)(&rtcpbuf)+rrlen, 200-rrlen, "janusvideo", 10);
2595
                                        if(sdeslen > 0) {
2596
                                                /* ... and then finally a REMB */
2597
                                                janus_rtcp_remb((char *)(&rtcpbuf)+rrlen+sdeslen, 24, participant->bitrate ? participant->bitrate : 256*1024);
2598
                                                gateway->relay_rtcp(msg->handle, 1, rtcpbuf, rrlen+sdeslen+24);
2599
                                        }
2600
                                }
2601
                                gboolean prev_recording_active = participant->recording_active;
2602
                                if(record) {
2603
                                        participant->recording_active = json_is_true(record);
2604
                                        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);
2605
                                }
2606
                                if(recfile) {
2607
                                        participant->recording_base = g_strdup(json_string_value(recfile));
2608
                                        JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", participant->recording_base, participant->room->room_id, participant->user_id);
2609
                                }
2610
                                /* Do we need to do something with the recordings right now? */
2611
                                if(participant->recording_active != prev_recording_active) {
2612
                                        /* Something changed */
2613
                                        if(!participant->recording_active) {
2614
                                                /* Not recording (anymore?) */
2615
                                                if(participant->arc) {
2616
                                                        janus_recorder_close(participant->arc);
2617
                                                        JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
2618
                                                        janus_recorder_free(participant->arc);
2619
                                                }
2620
                                                participant->arc = NULL;
2621
                                                if(participant->vrc) {
2622
                                                        janus_recorder_close(participant->vrc);
2623
                                                        JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
2624
                                                        janus_recorder_free(participant->vrc);
2625
                                                }
2626
                                                participant->vrc = NULL;
2627
                                        } else if(participant->recording_active && participant->sdp) {
2628
                                                /* We've started recording, send a PLI/FIR and go on */
2629
                                                char filename[255];
2630
                                                gint64 now = janus_get_monotonic_time();
2631
                                                if(strstr(participant->sdp, "m=audio")) {
2632
                                                        memset(filename, 0, 255);
2633
                                                        if(participant->recording_base) {
2634
                                                                /* Use the filename and path we have been provided */
2635
                                                                g_snprintf(filename, 255, "%s-audio", participant->recording_base);
2636
                                                                participant->arc = janus_recorder_create(NULL, 0, filename);
2637
                                                                if(participant->arc == NULL) {
2638
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2639
                                                                }
2640
                                                        } else {
2641
                                                                /* Build a filename */
2642
                                                                g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
2643
                                                                        participant->room->room_id, participant->user_id, now);
2644
                                                                participant->arc = janus_recorder_create(participant->room->rec_dir, 0, filename);
2645
                                                                if(participant->arc == NULL) {
2646
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
2647
                                                                }
2648
                                                        }
2649
                                                }
2650
                                                if(strstr(participant->sdp, "m=video")) {
2651
                                                        memset(filename, 0, 255);
2652
                                                        if(participant->recording_base) {
2653
                                                                /* Use the filename and path we have been provided */
2654
                                                                g_snprintf(filename, 255, "%s-video", participant->recording_base);
2655
                                                                participant->vrc = janus_recorder_create(NULL, 1, filename);
2656
                                                                if(participant->vrc == NULL) {
2657
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2658
                                                                }
2659
                                                        } else {
2660
                                                                /* Build a filename */
2661
                                                                g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
2662
                                                                        participant->room->room_id, participant->user_id, now);
2663
                                                                participant->vrc = janus_recorder_create(participant->room->rec_dir, 1, filename);
2664
                                                                if(participant->vrc == NULL) {
2665
                                                                        JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
2666
                                                                }
2667
                                                        }
2668
                                                        /* Send a FIR */
2669
                                                        char buf[20];
2670
                                                        memset(buf, 0, 20);
2671
                                                        janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
2672
                                                        JANUS_LOG(LOG_VERB, "Recording video, sending FIR to %"SCNu64" (%s)\n",
2673
                                                                participant->user_id, participant->display ? participant->display : "??");
2674
                                                        gateway->relay_rtcp(participant->session->handle, 1, buf, 20);
2675
                                                        /* Send a PLI too, just in case... */
2676
                                                        memset(buf, 0, 12);
2677
                                                        janus_rtcp_pli((char *)&buf, 12);
2678
                                                        JANUS_LOG(LOG_VERB, "Recording video, sending PLI to %"SCNu64" (%s)\n",
2679
                                                                participant->user_id, participant->display ? participant->display : "??");
2680
                                                        gateway->relay_rtcp(participant->session->handle, 1, buf, 12);
2681
                                                }
2682
                                        }
2683
                                }
2684
                                /* Done */
2685
                                event = json_object();
2686
                                json_object_set_new(event, "videoroom", json_string("event"));
2687
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2688
                                json_object_set_new(event, "configured", json_string("ok"));
2689
                        } else if(!strcasecmp(request_text, "unpublish")) {
2690
                                /* This participant wants to unpublish */
2691
                                if(!participant->sdp) {
2692
                                        JANUS_LOG(LOG_ERR, "Can't unpublish, not published\n");
2693
                                        error_code = JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED;
2694
                                        g_snprintf(error_cause, 512, "Can't unpublish, not published");
2695
                                        goto error;
2696
                                }
2697
                                /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
2698
                                gateway->close_pc(session->handle);
2699
                                /* Done */
2700
                                event = json_object();
2701
                                json_object_set_new(event, "videoroom", json_string("event"));
2702
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2703
                                json_object_set_new(event, "unpublished", json_string("ok"));
2704
                        } else if(!strcasecmp(request_text, "leave")) {
2705
                                /* This publisher is leaving, tell everybody */
2706
                                event = json_object();
2707
                                json_object_set_new(event, "videoroom", json_string("event"));
2708
                                json_object_set_new(event, "room", json_integer(participant->room->room_id));
2709
                                json_object_set_new(event, "leaving", json_integer(participant->user_id));
2710
                                char *leaving_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
2711
                                GHashTableIter iter;
2712
                                gpointer value;
2713
                                if(participant->room) {
2714
                                        if(!participant->room->destroyed) {
2715
                                                janus_mutex_lock(&participant->room->participants_mutex);
2716
                                                g_hash_table_iter_init(&iter, participant->room->participants);
2717
                                                while (!participant->room->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
2718
                                                        janus_videoroom_participant *p = value;
2719
                                                        if(p == participant) {
2720
                                                                continue;        /* Skip the new publisher itself */
2721
                                                        }
2722
                                                        JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2723
                                                        int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
2724
                                                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
2725
                                                }
2726
                                                janus_mutex_unlock(&participant->room->participants_mutex);
2727
                                        }
2728
                                }
2729
                                g_free(leaving_text);
2730
                                /* Done */
2731
                                participant->audio_active = FALSE;
2732
                                participant->video_active = FALSE;
2733
                                session->started = FALSE;
2734
                                //~ session->destroy = TRUE;
2735
                        } else {
2736
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
2737
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
2738
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
2739
                                goto error;
2740
                        }
2741
                } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2742
                        /* Handle this listener */
2743
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
2744
                        if(listener == NULL) {
2745
                                JANUS_LOG(LOG_ERR, "Invalid listener instance\n");
2746
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2747
                                g_snprintf(error_cause, 512, "Invalid listener instance");
2748
                                goto error;
2749
                        }
2750
                        if(!strcasecmp(request_text, "join")) {
2751
                                JANUS_LOG(LOG_ERR, "Already in as a listener on this handle\n");
2752
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
2753
                                g_snprintf(error_cause, 512, "Already in as a listener on this handle");
2754
                                goto error;
2755
                        } else if(!strcasecmp(request_text, "start")) {
2756
                                /* Start/restart receiving the publisher streams */
2757
                                janus_videoroom_participant *publisher = listener->feed;
2758
                                listener->paused = FALSE;
2759
                                event = json_object();
2760
                                json_object_set_new(event, "videoroom", json_string("event"));
2761
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2762
                                json_object_set_new(event, "started", json_string("ok"));
2763
                                if(publisher) {
2764
                                        /* Send a FIR */
2765
                                        char buf[20];
2766
                                        memset(buf, 0, 20);
2767
                                        janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
2768
                                        JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2769
                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
2770
                                        /* Send a PLI too, just in case... */
2771
                                        memset(buf, 0, 12);
2772
                                        janus_rtcp_pli((char *)&buf, 12);
2773
                                        JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2774
                                        gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
2775
                                }
2776
                        } else if(!strcasecmp(request_text, "configure")) {
2777
                                json_t *audio = json_object_get(root, "audio");
2778
                                if(audio && !json_is_boolean(audio)) {
2779
                                        JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
2780
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2781
                                        g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
2782
                                        goto error;
2783
                                }
2784
                                json_t *video = json_object_get(root, "video");
2785
                                if(video && !json_is_boolean(video)) {
2786
                                        JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
2787
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2788
                                        g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
2789
                                        goto error;
2790
                                }
2791
                                json_t *data = json_object_get(root, "data");
2792
                                if(data && !json_is_boolean(data)) {
2793
                                        JANUS_LOG(LOG_ERR, "Invalid element (data should be a boolean)\n");
2794
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2795
                                        g_snprintf(error_cause, 512, "Invalid value (data should be a boolean)");
2796
                                        goto error;
2797
                                }
2798
                                /* Update the audio/video/data flags, if set */
2799
                                janus_videoroom_participant *publisher = listener->feed;
2800
                                if(publisher) {
2801
                                        if(audio && publisher->audio)
2802
                                                listener->audio = json_is_true(audio);
2803
                                        if(video && publisher->video)
2804
                                                listener->video = json_is_true(video);
2805
                                        if(data && publisher->data)
2806
                                                listener->data = json_is_true(data);
2807
                                }
2808
                                event = json_object();
2809
                                json_object_set_new(event, "videoroom", json_string("event"));
2810
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2811
                                json_object_set_new(event, "configured", json_string("ok"));
2812
                        } else if(!strcasecmp(request_text, "pause")) {
2813
                                /* Stop receiving the publisher streams for a while */
2814
                                listener->paused = TRUE;
2815
                                event = json_object();
2816
                                json_object_set_new(event, "videoroom", json_string("event"));
2817
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2818
                                json_object_set_new(event, "paused", json_string("ok"));
2819
                        } else if(!strcasecmp(request_text, "switch")) {
2820
                                /* This listener wants to switch to a different publisher */
2821
                                json_t *feed = json_object_get(root, "feed");
2822
                                if(!feed) {
2823
                                        JANUS_LOG(LOG_ERR, "Missing element (feed)\n");
2824
                                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
2825
                                        g_snprintf(error_cause, 512, "Missing element (feed)");
2826
                                        goto error;
2827
                                }
2828
                                if(!json_is_integer(feed) || json_integer_value(feed) < 0) {
2829
                                        JANUS_LOG(LOG_ERR, "Invalid element (feed should be a positive integer)\n");
2830
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2831
                                        g_snprintf(error_cause, 512, "Invalid element (feed should be a positive integer)");
2832
                                        goto error;
2833
                                }
2834
                                guint64 feed_id = json_integer_value(feed);
2835
                                json_t *audio = json_object_get(root, "audio");
2836
                                if(audio && !json_is_boolean(audio)) {
2837
                                        JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
2838
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2839
                                        g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
2840
                                        goto error;
2841
                                }
2842
                                json_t *video = json_object_get(root, "video");
2843
                                if(video && !json_is_boolean(video)) {
2844
                                        JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
2845
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2846
                                        g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
2847
                                        goto error;
2848
                                }
2849
                                json_t *data = json_object_get(root, "data");
2850
                                if(data && !json_is_boolean(data)) {
2851
                                        JANUS_LOG(LOG_ERR, "Invalid element (data should be a boolean)\n");
2852
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2853
                                        g_snprintf(error_cause, 512, "Invalid value (data should be a boolean)");
2854
                                        goto error;
2855
                                }
2856
                                if(!listener->room) {
2857
                                        JANUS_LOG(LOG_ERR, "Room Destroyed \n");
2858
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2859
                                        g_snprintf(error_cause, 512, "No such room ");
2860
                                        goto error;
2861
                                }
2862
                                if(listener->room->destroyed) {
2863
                                        JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")\n", listener->room->room_id);
2864
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2865
                                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", listener->room->room_id);
2866
                                        goto error;
2867
                                }
2868
                                janus_mutex_lock(&listener->room->participants_mutex);
2869
                                janus_videoroom_participant *publisher = g_hash_table_lookup(listener->room->participants, GUINT_TO_POINTER(feed_id));
2870
                                janus_mutex_unlock(&listener->room->participants_mutex);
2871
                                if(publisher == NULL || publisher->sdp == NULL) {
2872
                                        JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
2873
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
2874
                                        g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
2875
                                        goto error;
2876
                                }
2877
                                gboolean paused = listener->paused;
2878
                                listener->paused = TRUE;
2879
                                /* Unsubscribe from the previous publisher */
2880
                                janus_videoroom_participant *prev_feed = listener->feed;
2881
                                if(prev_feed) {
2882
                                        janus_mutex_lock(&prev_feed->listeners_mutex);
2883
                                        prev_feed->listeners = g_slist_remove(prev_feed->listeners, listener);
2884
                                        janus_mutex_unlock(&prev_feed->listeners_mutex);
2885
                                        listener->feed = NULL;
2886
                                }
2887
                                /* Subscribe to the new one */
2888
                                listener->audio = audio ? json_is_true(audio) : TRUE;        /* True by default */
2889
                                if(!publisher->audio)
2890
                                        listener->audio = FALSE;        /* ... unless the publisher isn't sending any audio */
2891
                                listener->video = video ? json_is_true(video) : TRUE;        /* True by default */
2892
                                if(!publisher->video)
2893
                                        listener->video = FALSE;        /* ... unless the publisher isn't sending any video */
2894
                                listener->data = data ? json_is_true(data) : TRUE;        /* True by default */
2895
                                if(!publisher->data)
2896
                                        listener->data = FALSE;        /* ... unless the publisher isn't sending any data */
2897
                                janus_mutex_lock(&publisher->listeners_mutex);
2898
                                publisher->listeners = g_slist_append(publisher->listeners, listener);
2899
                                janus_mutex_unlock(&publisher->listeners_mutex);
2900
                                listener->feed = publisher;
2901
                                /* Send a FIR to the new publisher */
2902
                                char buf[20];
2903
                                memset(buf, 0, 20);
2904
                                janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
2905
                                JANUS_LOG(LOG_VERB, "Switching existing listener to new publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2906
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
2907
                                /* Send a PLI too, just in case... */
2908
                                memset(buf, 0, 12);
2909
                                janus_rtcp_pli((char *)&buf, 12);
2910
                                JANUS_LOG(LOG_VERB, "Switching existing listener to new publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
2911
                                gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
2912
                                /* Done */
2913
                                listener->paused = paused;
2914
                                event = json_object();
2915
                                json_object_set_new(event, "videoroom", json_string("event"));
2916
                                json_object_set_new(event, "switched", json_string("ok"));
2917
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2918
                                json_object_set_new(event, "id", json_integer(feed_id));
2919
                                if(publisher->display)
2920
                                        json_object_set_new(event, "display", json_string(publisher->display));
2921
                        } else if(!strcasecmp(request_text, "leave")) {
2922
                                janus_videoroom_participant *publisher = listener->feed;
2923
                                if(publisher != NULL) {
2924
                                        janus_mutex_lock(&publisher->listeners_mutex);
2925
                                        publisher->listeners = g_slist_remove(publisher->listeners, listener);
2926
                                        janus_mutex_unlock(&publisher->listeners_mutex);
2927
                                        listener->feed = NULL;
2928
                                }
2929
                                event = json_object();
2930
                                json_object_set_new(event, "videoroom", json_string("event"));
2931
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
2932
                                json_object_set_new(event, "left", json_string("ok"));
2933
                                session->started = FALSE;
2934
                        } else {
2935
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
2936
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
2937
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
2938
                                goto error;
2939
                        }
2940
                } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
2941
                        /* Handle this Multiplexed listener */
2942
                        janus_videoroom_listener_muxed *listener = (janus_videoroom_listener_muxed *)session->participant;
2943
                        if(listener == NULL) {
2944
                                JANUS_LOG(LOG_ERR, "Invalid Multiplexed listener instance\n");
2945
                                error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2946
                                g_snprintf(error_cause, 512, "Invalid Multiplexed listener instance");
2947
                                goto error;
2948
                        }
2949
                        if(!strcasecmp(request_text, "join")) {
2950
                                JANUS_LOG(LOG_ERR, "Already in as a Multiplexed listener on this handle\n");
2951
                                error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
2952
                                g_snprintf(error_cause, 512, "Already in as a Multiplexed listener on this handle");
2953
                                goto error;
2954
                        } else if(!strcasecmp(request_text, "add")) {
2955
                                /* Add new streams to subscribe to */
2956
                                GList *list = NULL;
2957
                                json_t *feeds = json_object_get(root, "feeds");
2958
                                if(!feeds) {
2959
                                        JANUS_LOG(LOG_ERR, "Missing element (feeds)\n");
2960
                                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
2961
                                        g_snprintf(error_cause, 512, "Missing element (feeds)");
2962
                                        goto error;
2963
                                }
2964
                                if(!json_is_array(feeds) || json_array_size(feeds) == 0) {
2965
                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds should be a non-empty array)\n");
2966
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2967
                                        g_snprintf(error_cause, 512, "Invalid element (feeds should be a non-empty array)");
2968
                                        goto error;
2969
                                }
2970
                                unsigned int i = 0;
2971
                                int problem = 0;
2972
                                if(!listener->room) {
2973
                                        JANUS_LOG(LOG_ERR, "Room Destroyed ");
2974
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2975
                                        g_snprintf(error_cause, 512, "No such room ");
2976
                                        goto error;
2977
                                }
2978
                                if(listener->room->destroyed) {
2979
                                        JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")", listener->room->room_id);
2980
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2981
                                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", listener->room->room_id);
2982
                                        goto error;
2983
                                }
2984
                                for(i=0; i<json_array_size(feeds); i++) {
2985
                                        json_t *feed = json_array_get(feeds, i);
2986
                                        if(listener->room->destroyed) {
2987
                                                problem = 1;
2988
                                                JANUS_LOG(LOG_ERR, "Room destroyed");
2989
                                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2990
                                                g_snprintf(error_cause, 512, "Room destroyed");
2991
                                                break;
2992
                                        }
2993
                                        if(!feed || !json_is_integer(feed)) {
2994
                                                problem = 1;
2995
                                                JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
2996
                                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2997
                                                g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
2998
                                                break;
2999
                                        }
3000
                                        uint64_t feed_id = json_integer_value(feed);
3001
                                        janus_mutex_lock(&listener->room->participants_mutex);
3002
                                        janus_videoroom_participant *publisher = g_hash_table_lookup(listener->room->participants, GUINT_TO_POINTER(feed_id));
3003
                                        janus_mutex_unlock(&listener->room->participants_mutex);
3004
                                        if(publisher == NULL) { //~ || publisher->sdp == NULL) {
3005
                                                /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
3006
                                                problem = 1;
3007
                                                JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
3008
                                                error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3009
                                                g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
3010
                                                break;
3011
                                        }
3012
                                        list = g_list_prepend(list, GUINT_TO_POINTER(feed_id));
3013
                                }
3014
                                if(problem) {
3015
                                        goto error;
3016
                                }
3017
                                list = g_list_reverse(list);
3018
                                if(janus_videoroom_muxed_subscribe(listener, list, msg->transaction) < 0) {
3019
                                        JANUS_LOG(LOG_ERR, "Error subscribing!\n");
3020
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
3021
                                        g_snprintf(error_cause, 512, "Error subscribing!");
3022
                                        goto error;
3023
                                }
3024
                                continue;
3025
                        } else if(!strcasecmp(request_text, "remove")) {
3026
                                /* Remove subscribed streams */
3027
                                GList *list = NULL;
3028
                                json_t *feeds = json_object_get(root, "feeds");
3029
                                if(!feeds) {
3030
                                        JANUS_LOG(LOG_ERR, "Missing element (feeds)\n");
3031
                                        error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
3032
                                        g_snprintf(error_cause, 512, "Missing element (feeds)");
3033
                                        goto error;
3034
                                }
3035
                                if(!json_is_array(feeds) || json_array_size(feeds) == 0) {
3036
                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds should be a non-empty array)\n");
3037
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3038
                                        g_snprintf(error_cause, 512, "Invalid element (feeds should be a non-empty array)");
3039
                                        goto error;
3040
                                }
3041
                                unsigned int i = 0;
3042
                                int error = 0;
3043
                                for(i=0; i<json_array_size(feeds); i++) {
3044
                                        json_t *feed = json_array_get(feeds, i);
3045
                                        if(!feed || !json_is_integer(feed)) {
3046
                                                error = 1;
3047
                                                break;
3048
                                        }
3049
                                        list = g_list_prepend(list, GUINT_TO_POINTER(json_integer_value(feed)));
3050
                                }
3051
                                if(error) {
3052
                                        JANUS_LOG(LOG_ERR, "Invalid element (feeds in the array must be integers)\n");
3053
                                        error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3054
                                        g_snprintf(error_cause, 512, "Invalid element (feeds in the array must be integers)");
3055
                                        goto error;
3056
                                }
3057
                                list = g_list_reverse(list);
3058
                                
3059
                                if(!listener->room) {
3060
                                        JANUS_LOG(LOG_ERR, "Error unsubscribing!\n");
3061
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
3062
                                        g_snprintf(error_cause, 512, "Error unsubscribing!");
3063
                                        goto error;
3064
                                }
3065
                                if(janus_videoroom_muxed_unsubscribe(listener, list, msg->transaction) < 0) {
3066
                                        JANUS_LOG(LOG_ERR, "Error unsubscribing!\n");
3067
                                        error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;        /* FIXME */
3068
                                        g_snprintf(error_cause, 512, "Error unsubscribing!");
3069
                                        goto error;
3070
                                }
3071
                                continue;
3072
                        } else if(!strcasecmp(request_text, "start")) {
3073
                                /* Start/restart receiving the publishers streams */
3074
                                /* TODO */
3075
                                event = json_object();
3076
                                json_object_set_new(event, "videoroom", json_string("event"));
3077
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3078
                                json_object_set_new(event, "started", json_string("ok"));
3079
                                //~ /* Send a FIR */
3080
                                //~ char buf[20];
3081
                                //~ memset(buf, 0, 20);
3082
                                //~ janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
3083
                                //~ JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3084
                                //~ gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
3085
                                //~ /* Send a PLI too, just in case... */
3086
                                //~ memset(buf, 0, 12);
3087
                                //~ janus_rtcp_pli((char *)&buf, 12);
3088
                                //~ JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %"SCNu64" (%s)\n", publisher->user_id, publisher->display ? publisher->display : "??");
3089
                                //~ gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
3090
                        } else if(!strcasecmp(request_text, "pause")) {
3091
                                /* Stop receiving the publishers streams for a while */
3092
                                /* TODO */
3093
                                event = json_object();
3094
                                json_object_set_new(event, "videoroom", json_string("event"));
3095
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3096
                                json_object_set_new(event, "paused", json_string("ok"));
3097
                        } else if(!strcasecmp(request_text, "leave")) {
3098
                                /* TODO */
3099
                                event = json_object();
3100
                                json_object_set_new(event, "videoroom", json_string("event"));
3101
                                json_object_set_new(event, "room", json_integer(listener->room->room_id));
3102
                                json_object_set_new(event, "left", json_string("ok"));
3103
                                session->started = FALSE;
3104
                        } else {
3105
                                JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
3106
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
3107
                                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
3108
                                goto error;
3109
                        }
3110
                }
3111

    
3112
                /* Prepare JSON event */
3113
                JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3114
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3115
                json_decref(event);
3116
                /* Any SDP to handle? */
3117
                if(!msg->sdp) {
3118
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
3119
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3120
                } else {
3121
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
3122
                        const char *type = NULL;
3123
                        if(!strcasecmp(msg->sdp_type, "offer")) {
3124
                                /* We need to answer */
3125
                                type = "answer";
3126
                        } else if(!strcasecmp(msg->sdp_type, "answer")) {
3127
                                /* We got an answer (from a listener?), no need to negotiate */
3128
                                int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
3129
                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3130
                        } else {
3131
                                /* TODO We don't support anything else right now... */
3132
                                JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg->sdp_type);
3133
                                error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
3134
                                g_snprintf(error_cause, 512, "Unknown SDP type '%s'", msg->sdp_type);
3135
                                goto error;
3136
                        }
3137
                        if(session->participant_type == janus_videoroom_p_type_publisher) {
3138
                                /* This is a new publisher: is there room? */
3139
                                janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
3140
                                janus_videoroom *videoroom = participant->room;
3141
                                int count = 0;
3142
                                GHashTableIter iter;
3143
                                gpointer value;
3144
                                if(!videoroom) {
3145
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3146
                                        goto error;
3147
                                }
3148
                                if(videoroom->destroyed) {
3149
                                        error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3150
                                        goto error;
3151
                                }
3152
                                janus_mutex_lock(&videoroom->participants_mutex);
3153
                                g_hash_table_iter_init(&iter, videoroom->participants);
3154
                                while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3155
                                        janus_videoroom_participant *p = value;
3156
                                        if(p != participant && p->sdp)
3157
                                                count++;
3158
                                }
3159
                                janus_mutex_unlock(&videoroom->participants_mutex);
3160
                                if(count == videoroom->max_publishers) {
3161
                                        participant->audio_active = FALSE;
3162
                                        participant->video_active = FALSE;
3163
                                        JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
3164
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3165
                                        g_snprintf(error_cause, 512, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
3166
                                        goto error;
3167
                                }
3168
                                /* Now prepare the SDP to give back */
3169
                                if(strstr(msg->sdp, "Mozilla")) {
3170
                                        participant->firefox = TRUE;
3171
                                }
3172
                                /* Which media are available? */
3173
                                int audio = 0, video = 0, data = 0;
3174
                                const char *audio_mode = NULL, *video_mode = NULL;
3175
                                sdp_parser_t *parser = sdp_parse(sdphome, msg->sdp, strlen(msg->sdp), 0);
3176
                                sdp_session_t *parsed_sdp = sdp_session(parser);
3177
                                if(!parsed_sdp) {
3178
                                        /* Invalid SDP */
3179
                                        JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdp_parsing_error(parser));
3180
                                        error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
3181
                                        g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdp_parsing_error(parser));
3182
                                        sdp_parser_free(parser);
3183
                                        goto error;
3184
                                }
3185
                                sdp_media_t *m = parsed_sdp->sdp_media;
3186
                                while(m) {
3187
                                        if(m->m_type == sdp_media_audio && m->m_port > 0) {
3188
                                                audio++;
3189
                                                participant->audio = TRUE;
3190
                                                if(audio > 1) {
3191
                                                        m = m->m_next;
3192
                                                        continue;
3193
                                                }
3194
                                        } else if(m->m_type == sdp_media_video && m->m_port > 0) {
3195
                                                video++;
3196
                                                participant->video = TRUE;
3197
                                                if(video > 1) {
3198
                                                        m = m->m_next;
3199
                                                        continue;
3200
                                                }
3201
#ifdef HAVE_SCTP
3202
                                        } else if(m->m_type == sdp_media_application && m->m_port > 0) {
3203
                                                data++;
3204
                                                participant->data = TRUE;
3205
                                                if(data > 1) {
3206
                                                        m = m->m_next;
3207
                                                        continue;
3208
                                                }
3209
#endif
3210
                                        }
3211
                                        if(m->m_type != sdp_media_application) {
3212
                                                /* What is the direction? */
3213
                                                switch(m->m_mode) {
3214
                                                        case sdp_recvonly:
3215
                                                                /* If we're getting a 'recvonly' publisher, we're going to answer with 'inactive' */
3216
                                                        case sdp_inactive:
3217
                                                                if(m->m_type == sdp_media_audio) {
3218
                                                                        audio_mode = "inactive";
3219
                                                                } else {
3220
                                                                        video_mode = "inactive";
3221
                                                                }
3222
                                                                break;
3223
                                                        case sdp_sendonly:
3224
                                                                /* What we expect, turn this into 'recvonly' */
3225
                                                        case sdp_sendrecv:
3226
                                                        default:
3227
                                                                if(m->m_type == sdp_media_audio) {
3228
                                                                        audio_mode = "recvonly";
3229
                                                                } else {
3230
                                                                        video_mode = "recvonly";
3231
                                                                }
3232
                                                                break;
3233
                                                }
3234
                                        }
3235
                                        m = m->m_next;
3236
                                }
3237
                                sdp_parser_free(parser);
3238
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", audio ? "is" : "is NOT");
3239
                                if(audio) {
3240
                                        JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", audio_mode);
3241
                                }
3242
                                JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", video ? "is" : "is NOT");
3243
                                if(video) {
3244
                                        JANUS_LOG(LOG_VERB, "  -- Will answer with media direction '%s'\n", video_mode);
3245
                                }
3246
                                JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", data ? "is" : "is NOT");
3247
                                /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
3248
                                int b = 0;
3249
                                if(participant->firefox)        /* Don't add any b=AS attribute for Chrome */
3250
                                        b = (int)(videoroom->bitrate/1000);
3251
                                char sdp[1280], audio_mline[256], video_mline[512], data_mline[256];
3252
                                if(audio) {
3253
                                        g_snprintf(audio_mline, 256, sdp_a_template,
3254
                                                OPUS_PT,                                                /* Opus payload type */
3255
                                                audio_mode,                                                /* The publisher gets a recvonly or inactive back */
3256
                                                OPUS_PT);                                                 /* Opus payload type */
3257
                                } else {
3258
                                        audio_mline[0] = '\0';
3259
                                }
3260
                                if(video) {
3261
                                        g_snprintf(video_mline, 512, sdp_v_template,
3262
                                                VP8_PT,                                                        /* VP8 payload type */
3263
                                                b,                                                                /* Bandwidth */
3264
                                                video_mode,                                                /* The publisher gets a recvonly or inactive back */
3265
                                                VP8_PT,                                                 /* VP8 payload type */
3266
                                                VP8_PT,                                                 /* VP8 payload type */
3267
                                                VP8_PT,                                                 /* VP8 payload type */
3268
                                                VP8_PT,                                                 /* VP8 payload type */
3269
                                                VP8_PT);                                                 /* VP8 payload type */
3270
                                } else {
3271
                                        video_mline[0] = '\0';
3272
                                }
3273
                                if(data) {
3274
                                        g_snprintf(data_mline, 256, sdp_d_template);
3275
                                } else {
3276
                                        data_mline[0] = '\0';
3277
                                }
3278
                                g_snprintf(sdp, 1280, sdp_template,
3279
                                        janus_get_monotonic_time(),                /* We need current time here */
3280
                                        janus_get_monotonic_time(),                /* We need current time here */
3281
                                        participant->room->room_name,        /* Video room name */
3282
                                        audio_mline,                                        /* Audio m-line, if any */
3283
                                        video_mline,                                        /* Video m-line, if any */
3284
                                        data_mline);                                        /* Data channel m-line, if any */
3285

    
3286
                                char *newsdp = g_strdup(sdp);
3287
                                if(video && b == 0) {
3288
                                        /* Remove useless bandwidth attribute */
3289
                                        newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
3290
                                }
3291
                                /* Is this room recorded? */
3292
                                if(videoroom->record || participant->recording_active) {
3293
                                        char filename[255];
3294
                                        gint64 now = janus_get_monotonic_time();
3295
                                        if(audio) {
3296
                                                memset(filename, 0, 255);
3297
                                                if(participant->recording_base) {
3298
                                                        /* Use the filename and path we have been provided */
3299
                                                        g_snprintf(filename, 255, "%s-audio", participant->recording_base);
3300
                                                        participant->arc = janus_recorder_create(NULL, 0, filename);
3301
                                                        if(participant->arc == NULL) {
3302
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
3303
                                                        }
3304
                                                } else {
3305
                                                        /* Build a filename */
3306
                                                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
3307
                                                                videoroom->room_id, participant->user_id, now);
3308
                                                        participant->arc = janus_recorder_create(videoroom->rec_dir, 0, filename);
3309
                                                        if(participant->arc == NULL) {
3310
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
3311
                                                        }
3312
                                                }
3313
                                        }
3314
                                        if(video) {
3315
                                                memset(filename, 0, 255);
3316
                                                if(participant->recording_base) {
3317
                                                        /* Use the filename and path we have been provided */
3318
                                                        g_snprintf(filename, 255, "%s-video", participant->recording_base);
3319
                                                        participant->vrc = janus_recorder_create(NULL, 1, filename);
3320
                                                        if(participant->vrc == NULL) {
3321
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
3322
                                                        }
3323
                                                } else {
3324
                                                        /* Build a filename */
3325
                                                        g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
3326
                                                                videoroom->room_id, participant->user_id, now);
3327
                                                        participant->vrc = janus_recorder_create(videoroom->rec_dir, 1, filename);
3328
                                                        if(participant->vrc == NULL) {
3329
                                                                JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
3330
                                                        }
3331
                                                }
3332
                                        }
3333
                                }
3334

    
3335
                                JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, newsdp);
3336
                                /* How long will the gateway take to push the event? */
3337
                                gint64 start = janus_get_monotonic_time();
3338
                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, newsdp);
3339
                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3340
                                if(strstr(newsdp, "recvonly"))
3341
                                        newsdp = janus_string_replace(newsdp, "recvonly", "sendonly");
3342
                                if(res != JANUS_OK) {
3343
                                        /* TODO Failed to negotiate? We should remove this publisher */
3344
                                } else {
3345
                                        /* Store the participant's SDP for interested listeners */
3346
                                        participant->sdp = newsdp;
3347
                                        /* Notify all other participants that there's a new boy in town */
3348
                                        json_t *list = json_array();
3349
                                        json_t *pl = json_object();
3350
                                        json_object_set_new(pl, "id", json_integer(participant->user_id));
3351
                                        if(participant->display)
3352
                                                json_object_set_new(pl, "display", json_string(participant->display));
3353
                                        json_array_append_new(list, pl);
3354
                                        json_t *pub = json_object();
3355
                                        json_object_set_new(pub, "videoroom", json_string("event"));
3356
                                        json_object_set_new(pub, "room", json_integer(participant->room->room_id));
3357
                                        json_object_set_new(pub, "publishers", list);
3358
                                        char *pub_text = json_dumps(pub, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3359
                                        json_decref(pub);
3360
                                        GHashTableIter iter;
3361
                                        gpointer value;
3362
                                        janus_mutex_lock(&videoroom->participants_mutex);
3363
                                        g_hash_table_iter_init(&iter, videoroom->participants);
3364
                                        while (!videoroom->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
3365
                                                janus_videoroom_participant *p = value;
3366
                                                if(p == participant) {
3367
                                                        continue;        /* Skip the new publisher itself */
3368
                                                }
3369
                                                JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
3370
                                                int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub_text, NULL, NULL);
3371
                                                JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3372
                                        }
3373
                                        g_free(pub_text);
3374
                                        janus_mutex_unlock(&videoroom->participants_mutex);
3375
                                        /* Let's wait for the setup_media event */
3376
                                }
3377
                        } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
3378
                                /* Negotiate by sending the selected publisher SDP back */
3379
                                janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
3380
                                /* FIXME We should handle the case where the participant has no SDP... */
3381
                                if(listener != NULL) {
3382
                                        janus_videoroom_participant *feed = (janus_videoroom_participant *)listener->feed;
3383
                                        if(feed != NULL && feed->sdp != NULL) {
3384
                                                /* How long will the gateway take to push the event? */
3385
                                                gint64 start = janus_get_monotonic_time();
3386
                                                int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, feed->sdp);
3387
                                                JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3388
                                                if(res != JANUS_OK) {
3389
                                                        /* TODO Failed to negotiate? We should remove this listener */
3390
                                                } else {
3391
                                                        /* Let's wait for the setup_media event */
3392
                                                }
3393
                                        }
3394
                                }
3395
                        } else if(session->participant_type == janus_videoroom_p_type_subscriber_muxed) {
3396
                                /* FIXME We shouldn't be here, we always offer ourselves */
3397
                        }
3398
                }
3399
                g_free(event_text);
3400
                janus_videoroom_message_free(msg);
3401

    
3402
                continue;
3403
                
3404
error:
3405
                {
3406
                        /* Prepare JSON error event */
3407
                        json_t *event = json_object();
3408
                        json_object_set_new(event, "videoroom", json_string("event"));
3409
                        json_object_set_new(event, "error_code", json_integer(error_code));
3410
                        json_object_set_new(event, "error", json_string(error_cause));
3411
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3412
                        json_decref(event);
3413
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
3414
                        int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
3415
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
3416
                        g_free(event_text);
3417
                        janus_videoroom_message_free(msg);
3418
                }
3419
        }
3420
        g_free(error_cause);
3421
        JANUS_LOG(LOG_VERB, "Leaving VideoRoom handler thread\n");
3422
        return NULL;
3423
}
3424

    
3425

    
3426
/* Multiplexing helpers */
3427
int janus_videoroom_muxed_subscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
3428
        if(!muxed_listener || !feeds)
3429
                return -1;
3430
        janus_mutex_lock(&muxed_listener->listeners_mutex);
3431
        JANUS_LOG(LOG_VERB, "Subscribing to %d feeds\n", g_list_length(feeds));
3432
        janus_videoroom *videoroom = muxed_listener->room;
3433
        GList *ps = feeds;
3434
        json_t *list = json_array();
3435
        int added_feeds = 0;
3436
        while(ps) {
3437
                uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
3438
                janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
3439
                if(publisher == NULL) { //~ || publisher->sdp == NULL) {
3440
                        /* FIXME For muxed listeners, we accept subscriptions to existing participants who haven't published yet */
3441
                        JANUS_LOG(LOG_WARN, "No such feed (%"SCNu64"), skipping\n", feed_id);
3442
                        ps = ps->next;
3443
                        continue;
3444
                }
3445
                /* Are we already subscribed? */
3446
                gboolean subscribed = FALSE;
3447
                GSList *ls = muxed_listener->listeners;
3448
                while(ls) {
3449
                        janus_videoroom_listener *l = (janus_videoroom_listener *)ls->data;
3450
                        if(l && (l->feed == publisher)) {
3451
                                subscribed = TRUE;
3452
                                JANUS_LOG(LOG_WARN, "Already subscribed to feed %"SCNu64", skipping\n", feed_id);
3453
                                break;
3454
                        }
3455
                        ls = ls->next;
3456
                }
3457
                if(subscribed) {
3458
                        ps = ps->next;
3459
                        continue;
3460
                }
3461
                janus_videoroom_listener *listener = calloc(1, sizeof(janus_videoroom_listener));
3462
                if(listener == NULL) {
3463
                        JANUS_LOG(LOG_FATAL, "Memory error!\n");
3464
                        ps = ps->next;
3465
                        continue;
3466
                }
3467
                listener->session = muxed_listener->session;
3468
                listener->room = videoroom;
3469
                listener->feed = publisher;
3470
                //~ listener->paused = TRUE;        /* We need an explicit start from the listener */
3471
                listener->paused = FALSE;
3472
                listener->parent = muxed_listener;
3473
                janus_mutex_lock(&publisher->listeners_mutex);
3474
                publisher->listeners = g_slist_append(publisher->listeners, listener);
3475
                janus_mutex_unlock(&publisher->listeners_mutex);
3476
                muxed_listener->listeners = g_slist_append(muxed_listener->listeners, listener);
3477
                JANUS_LOG(LOG_VERB, "Now subscribed to %d feeds\n", g_slist_length(muxed_listener->listeners));
3478
                /* Add to feeds in the answer */
3479
                added_feeds++;
3480
                json_t *f = json_object();
3481
                json_object_set_new(f, "id", json_integer(feed_id));
3482
                if(publisher->display)
3483
                        json_object_set_new(f, "display", json_string(publisher->display));
3484
                json_array_append_new(list, f);
3485
                ps = ps->next;
3486
        }
3487
        janus_mutex_unlock(&muxed_listener->listeners_mutex);
3488
        if(added_feeds == 0) {
3489
                /* Nothing changed */
3490
                return 0;
3491
        }
3492
        /* Prepare event */
3493
        json_t *event = json_object();
3494
        json_object_set_new(event, "videoroom", json_string("muxed-attached"));
3495
        json_object_set_new(event, "room", json_integer(videoroom->room_id));
3496
        json_object_set_new(event, "feeds", list);
3497
        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3498
        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3499
        json_decref(event);
3500
        /* Send the updated offer */
3501
        return janus_videoroom_muxed_offer(muxed_listener, transaction, event_text);
3502
}
3503

    
3504
int janus_videoroom_muxed_unsubscribe(janus_videoroom_listener_muxed *muxed_listener, GList *feeds, char *transaction) {
3505
        janus_mutex_lock(&muxed_listener->listeners_mutex);
3506
        JANUS_LOG(LOG_VERB, "Unsubscribing from %d feeds\n", g_list_length(feeds));
3507
        janus_videoroom *videoroom = muxed_listener->room;
3508
        GList *ps = feeds;
3509
        json_t *list = json_array();
3510
        int removed_feeds = 0;
3511
        while(ps) {
3512
                uint64_t feed_id = GPOINTER_TO_UINT(ps->data);
3513
                GSList *ls = muxed_listener->listeners;
3514
                while(ls) {
3515
                        janus_videoroom_listener *listener = (janus_videoroom_listener *)ls->data;
3516
                        if(listener) {
3517
                                janus_videoroom_participant *publisher = listener->feed;
3518
                                if(publisher == NULL || publisher->user_id != feed_id) {
3519
                                        /* Not the publisher we're looking for */
3520
                                        ls = ls->next;
3521
                                        continue;
3522
                                }
3523
                                janus_mutex_lock(&publisher->listeners_mutex);
3524
                                publisher->listeners = g_slist_remove(publisher->listeners, listener);
3525
                                janus_mutex_unlock(&publisher->listeners_mutex);
3526
                                listener->feed = NULL;
3527
                                muxed_listener->listeners = g_slist_remove(muxed_listener->listeners, listener);
3528
                                JANUS_LOG(LOG_VERB, "Now subscribed to %d feeds\n", g_slist_length(muxed_listener->listeners));
3529
                                /* Add to feeds in the answer */
3530
                                removed_feeds++;
3531
                                json_t *f = json_object();
3532
                                json_object_set_new(f, "id", json_integer(feed_id));
3533
                                json_array_append_new(list, f);
3534
                                break;
3535
                        }
3536
                        ls = ls->next;
3537
                }
3538
                ps = ps->next;
3539
        }
3540
        janus_mutex_unlock(&muxed_listener->listeners_mutex);
3541
        if(removed_feeds == 0) {
3542
                /* Nothing changed */
3543
                return 0;
3544
        }
3545
        /* Prepare event */
3546
        json_t *event = json_object();
3547
        json_object_set_new(event, "videoroom", json_string("muxed-detached"));
3548
        json_object_set_new(event, "room", json_integer(videoroom->room_id));
3549
        json_object_set_new(event, "feeds", list);
3550
        JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
3551
        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
3552
        json_decref(event);
3553
        /* Send the updated offer */
3554
        return janus_videoroom_muxed_offer(muxed_listener, transaction, event_text);
3555
}
3556

    
3557
int janus_videoroom_muxed_offer(janus_videoroom_listener_muxed *muxed_listener, char *transaction, char *event_text) {
3558
        if(muxed_listener == NULL)
3559
                return -1;
3560
        /* Negotiate by placing a 'muxed' fake attribute for each publisher we subscribed to,
3561
         * that will translate to multiple SSRCs when merging the SDP */
3562
        int audio = 0, video = 0;
3563
        char audio_muxed[1024], video_muxed[1024], temp[255];
3564
        char sdp[2048], audio_mline[512], video_mline[512], data_mline[1];
3565
        data_mline[0] = '\0'; /* Multiplexed streams do not support data channels */
3566
        memset(audio_muxed, 0, 1024);
3567
        memset(video_muxed, 0, 1024);
3568
        memset(audio_mline, 0, 512);
3569
        memset(video_mline, 0, 512);
3570
        /* Prepare the m-lines (FIXME this will result in an audio line even for video-only rooms, but we don't care) */
3571
        g_snprintf(audio_mline, 512, sdp_a_template,
3572
                OPUS_PT,                                                /* Opus payload type */
3573
                "sendonly",                                                /* The publisher gets a recvonly back */
3574
                OPUS_PT);                                                 /* Opus payload type */
3575
        g_snprintf(video_mline, 512, sdp_v_template,
3576
                VP8_PT,                                                        /* VP8 payload type */
3577
                0,                                                                /* Bandwidth */
3578
                "sendonly",                                                /* The publisher gets a recvonly back */
3579
                VP8_PT,                                                 /* VP8 payload type */
3580
                VP8_PT,                                                 /* VP8 payload type */
3581
                VP8_PT,                                                 /* VP8 payload type */
3582
                VP8_PT,                                                 /* VP8 payload type */
3583
                VP8_PT);                                                 /* VP8 payload type */
3584
        /* FIXME Add a fake user/SSRC just to avoid the "Failed to set max send bandwidth for video content" bug */
3585
        g_strlcat(audio_muxed, "a=planb:sfu0 1\r\n", 1024);
3586
        g_strlcat(video_muxed, "a=planb:sfu0 2\r\n", 1024);
3587
        /* Go through all the available publishers */
3588
        GSList *ps = muxed_listener->listeners;
3589
        while(ps) {
3590
                janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
3591
                if(l && l->feed) { //~ && l->feed->sdp) {
3592
                        if(strstr(l->feed->sdp, "m=audio")) {
3593
                                audio++;
3594
                                g_snprintf(temp, 255, "a=planb:sfu%"SCNu64" %"SCNu32"\r\n", l->feed->user_id, l->feed->audio_ssrc);
3595
                                g_strlcat(audio_muxed, temp, 1024);
3596
                        }
3597
                        if(strstr(l->feed->sdp, "m=video")) {
3598
                                video++;
3599
                                g_snprintf(temp, 255, "a=planb:sfu%"SCNu64" %"SCNu32"\r\n", l->feed->user_id, l->feed->video_ssrc);
3600
                                g_strlcat(video_muxed, temp, 1024);
3601
                        }
3602
                }
3603
                ps = ps->next;
3604
        }
3605
        /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
3606
        if(audio) {
3607
                g_strlcat(audio_mline, audio_muxed, 2048);
3608
        }
3609
        if(video) {
3610
                g_strlcat(video_mline, video_muxed, 2048);
3611
        }
3612
        g_snprintf(sdp, 2048, sdp_template,
3613
                janus_get_monotonic_time(),                /* We need current time here */
3614
                janus_get_monotonic_time(),                /* We need current time here */
3615
                muxed_listener->room->room_name,        /* Video room name */
3616
                audio_mline,                                        /* Audio m-line */
3617
                video_mline,                                        /* Video m-line */
3618
                data_mline);                                        /* Data channel m-line */
3619
        char *newsdp = g_strdup(sdp);
3620
        if(video) {
3621
                /* Remove useless bandwidth attribute, if any */
3622
                newsdp = janus_string_replace(newsdp, "b=AS:0\r\n", "");
3623
        }
3624
        JANUS_LOG(LOG_VERB, "%s", newsdp);
3625
        /* How long will the gateway take to push the event? */
3626
        gint64 start = janus_get_monotonic_time();
3627
        int res = gateway->push_event(muxed_listener->session->handle, &janus_videoroom_plugin, transaction, event_text, "offer", newsdp);
3628
        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
3629
        if(res != JANUS_OK) {
3630
                /* TODO Failed to negotiate? We should remove this listener */
3631
        } else {
3632
                /* Let's wait for the setup_media event */
3633
        }
3634
        return 0;
3635
}
3636

    
3637

    
3638
/* Helper to quickly relay RTP packets from publishers to subscribers */
3639
static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
3640
        janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
3641
        if(!packet || !packet->data || packet->length < 1) {
3642
                JANUS_LOG(LOG_ERR, "Invalid packet...\n");
3643
                return;
3644
        }
3645
        janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
3646
        if(!listener || !listener->session) {
3647
                // JANUS_LOG(LOG_ERR, "Invalid session...\n");
3648
                return;
3649
        }
3650
        if(listener->paused) {
3651
                // JANUS_LOG(LOG_ERR, "This listener paused the stream...\n");
3652
                return;
3653
        }
3654
        janus_videoroom_session *session = listener->session;
3655
        if(!session || !session->handle) {
3656
                // JANUS_LOG(LOG_ERR, "Invalid session...\n");
3657
                return;
3658
        }
3659
        if(!session->started) {
3660
                // JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
3661
                return;
3662
        }
3663
        
3664
        /* Make sure there hasn't been a publisher switch by checking the SSRC */
3665
        if(packet->is_video) {
3666
                /* Check if this listener is subscribed to this medium */
3667
                if(!listener->video) {
3668
                        /* Nope, don't relay */
3669
                        return;
3670
                }
3671
                if(ntohl(packet->data->ssrc) != listener->context.v_last_ssrc) {
3672
                        listener->context.v_last_ssrc = ntohl(packet->data->ssrc);
3673
                        listener->context.v_base_ts_prev = listener->context.v_last_ts;
3674
                        listener->context.v_base_ts = packet->timestamp;
3675
                        listener->context.v_base_seq_prev = listener->context.v_last_seq;
3676
                        listener->context.v_base_seq = packet->seq_number;
3677
                }
3678
                /* Compute a coherent timestamp and sequence number */
3679
                listener->context.v_last_ts = (packet->timestamp-listener->context.v_base_ts)
3680
                        + listener->context.v_base_ts_prev+4500;        /* FIXME When switching, we assume 15fps */
3681
                listener->context.v_last_seq = (packet->seq_number-listener->context.v_base_seq)+listener->context.v_base_seq_prev+1;
3682
                /* Update the timestamp and sequence number in the RTP packet, and send it */
3683
                packet->data->timestamp = htonl(listener->context.v_last_ts);
3684
                packet->data->seq_number = htons(listener->context.v_last_seq);
3685
                if(gateway != NULL)
3686
                        gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
3687
                /* Restore the timestamp and sequence number to what the publisher set them to */
3688
                packet->data->timestamp = htonl(packet->timestamp);
3689
                packet->data->seq_number = htons(packet->seq_number);
3690
        } else {
3691
                /* Check if this listener is subscribed to this medium */
3692
                if(!listener->audio) {
3693
                        /* Nope, don't relay */
3694
                        return;
3695
                }
3696
                if(ntohl(packet->data->ssrc) != listener->context.a_last_ssrc) {
3697
                        listener->context.a_last_ssrc = ntohl(packet->data->ssrc);
3698
                        listener->context.a_base_ts_prev = listener->context.a_last_ts;
3699
                        listener->context.a_base_ts = packet->timestamp;
3700
                        listener->context.a_base_seq_prev = listener->context.a_last_seq;
3701
                        listener->context.a_base_seq = packet->seq_number;
3702
                }
3703
                /* Compute a coherent timestamp and sequence number */
3704
                listener->context.a_last_ts = (packet->timestamp-listener->context.a_base_ts)
3705
                        + listener->context.a_base_ts_prev+960;        /* FIXME When switching, we assume Opus and so a 960 ts step */
3706
                listener->context.a_last_seq = (packet->seq_number-listener->context.a_base_seq)+listener->context.a_base_seq_prev+1;
3707
                /* Update the timestamp and sequence number in the RTP packet, and send it */
3708
                packet->data->timestamp = htonl(listener->context.a_last_ts);
3709
                packet->data->seq_number = htons(listener->context.a_last_seq);
3710
                if(gateway != NULL)
3711
                        gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
3712
                /* Restore the timestamp and sequence number to what the publisher set them to */
3713
                packet->data->timestamp = htonl(packet->timestamp);
3714
                packet->data->seq_number = htons(packet->seq_number);
3715
        }
3716

    
3717
        return;
3718
}
3719

    
3720
static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data) {
3721
        janus_videoroom_data_relay_packet *packet = (janus_videoroom_data_relay_packet *)user_data;
3722
        janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
3723
        if(!listener || !listener->session || !listener->data || listener->paused) {
3724
                return;
3725
        }
3726
        janus_videoroom_session *session = listener->session;
3727
        if(!session || !session->handle) {
3728
                return;
3729
        }
3730
        if(!session->started) {
3731
                return;
3732
        }
3733
        if(gateway != NULL) {
3734
                char text[1<<16];
3735
                memset(text, 0, 1<<16);
3736
                memcpy(text, packet->data, packet->length);
3737
                text[packet->length] = '\0';
3738
                JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
3739
                gateway->relay_data(session->handle, text, strlen(text));
3740
        }
3741
        return;
3742
}
3743

    
3744
/* Helper to free janus_videoroom structs. */
3745
static void janus_videoroom_free(janus_videoroom *room) {
3746
        if(room) {
3747
                janus_mutex_lock(&room->participants_mutex);
3748
                g_free(room->room_name);
3749
                g_free(room->room_secret);
3750
                g_free(room->rec_dir);
3751
                g_hash_table_unref(room->participants);
3752
                janus_mutex_unlock(&room->participants_mutex);
3753
                janus_mutex_destroy(&room->participants_mutex);
3754
                free(room);
3755
                room = NULL;
3756
        }
3757
}
3758

    
3759
static void janus_videoroom_listener_free(janus_videoroom_listener *l) {
3760
        JANUS_LOG(LOG_VERB, "Freeing listener\n");
3761
        free(l);
3762
}
3763

    
3764
static void janus_videoroom_muxed_listener_free(janus_videoroom_listener_muxed *l) {
3765
        JANUS_LOG(LOG_VERB, "Freeing muxed-listener\n");
3766
        free(l);
3767
}
3768

    
3769
static void janus_videoroom_participant_free(janus_videoroom_participant *p) {
3770
        JANUS_LOG(LOG_VERB, "Freeing publisher\n");
3771
        g_free(p->display);
3772
        g_free(p->sdp);
3773

    
3774
        if(p->arc) {
3775
                janus_recorder_free(p->arc);
3776
                p->arc = NULL;
3777
        }
3778
        if(p->vrc) {
3779
                janus_recorder_free(p->vrc);
3780
                p->vrc = NULL;
3781
        }
3782

    
3783
        janus_mutex_lock(&p->listeners_mutex);
3784
        while(p->listeners) {
3785
                janus_videoroom_listener *l = (janus_videoroom_listener *)p->listeners->data;
3786
                if(l) {
3787
                        p->listeners = g_slist_remove(p->listeners, l);
3788
                        l->feed = NULL;
3789
                }
3790
        }
3791
        janus_mutex_unlock(&p->listeners_mutex);
3792
        janus_mutex_lock(&p->rtp_forwarders_mutex);
3793
        if(p->udp_sock > 0) {
3794
                close(p->udp_sock);
3795
                p->udp_sock = 0;
3796
        }
3797
        g_hash_table_destroy(p->rtp_forwarders);
3798
        p->rtp_forwarders = NULL;
3799
        janus_mutex_unlock(&p->rtp_forwarders_mutex);
3800
        g_slist_free(p->listeners);
3801

    
3802
        janus_mutex_destroy(&p->listeners_mutex);
3803
        janus_mutex_destroy(&p->rtp_forwarders_mutex);
3804
        free(p);
3805
}