Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_recordplay.c @ 8241c758

History | View | Annotate | Download (73.9 KB)

1
/*! \file   janus_recordplay.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief  Janus Record&Play plugin
5
 * \details  This is a simple application that implements two different
6
 * features: it allows you to record a message you send with WebRTC in
7
 * the format defined in recorded.c (MJR recording) and subsequently
8
 * replay this recording (or other previously recorded) through WebRTC
9
 * as well.
10
 * 
11
 * This application aims at showing how easy recording frames sent by
12
 * a peer is, and how this recording can be re-used directly, without
13
 * necessarily involving a post-processing process (e.g., through the
14
 * tool we provide in janus-pp-rec.c).
15
 * 
16
 * The configuration process is quite easy: just choose where the
17
 * recordings should be saved. The same folder will also be used to list
18
 * the available recordings that can be replayed.
19
 * 
20
 * \note The application creates a special file in INI format with
21
 * \c .nfo extension for each recording that is saved. This is necessary
22
 * to map a specific audio .mjr file to a different video .mjr one, as
23
 * they always get saved in different files. If you want to replay
24
 * recordings you took in a different application (e.g., the streaming
25
 * or videoroom plugins) just copy the related files in the folder you
26
 * configured this plugin to use and create a .nfo file in the same
27
 * folder to create a mapping, e.g.:
28
 * 
29
 *                 [12345678]
30
 *                 name = My videoroom recording
31
 *                 date = 2014-10-14 17:11:26
32
 *                 audio = mcu-audio.mjr
33
 *                 video = mcu-video.mjr
34
 * 
35
 * \section recplayapi Record&Play API
36
 * 
37
 * The Record&Play API supports several requests, some of which are
38
 * synchronous and some asynchronous. There are some situations, though,
39
 * (invalid JSON, invalid request) which will always result in a
40
 * synchronous error response even for asynchronous requests. 
41
 * 
42
 * \c list and \c update are synchronous requests, which means you'll
43
 * get a response directly within the context of the transaction. \c list
44
 * lists all the available recordings, while \c update forces the plugin
45
 * to scan the folder of recordings again in case some were added manually
46
 * and not indexed in the meanwhile.
47
 * 
48
 * The \c record , \c play , \c start and \c stop requests instead are
49
 * all asynchronous, which means you'll get a notification about their
50
 * success or failure in an event. \c record asks the plugin to start
51
 * recording a session; \c play asks the plugin to prepare the playout
52
 * of one of the previously recorded sessions; \c start starts the
53
 * actual playout, and \c stop stops whatever the session was for, i.e.,
54
 * recording or replaying.
55
 * 
56
 * The \c list request has to be formatted as follows:
57
 *
58
\verbatim
59
{
60
        "request" : "list"
61
}
62
\endverbatim
63
 *
64
 * A successful request will result in an array of recordings:
65
 * 
66
\verbatim
67
{
68
        "recordplay" : "list",
69
        "list": [        // Array of recording objects
70
                {                        // Recording #1
71
                        "id": <numeric ID>,
72
                        "name": "<Name of the recording>",
73
                        "date": "<Date of the recording>",
74
                        "audio": "<Audio rec file, if any; optional>",
75
                        "video": "<Video rec file, if any; optional>"
76
                },
77
                <other recordings>
78
        ]
79
}
80
\endverbatim
81
 * 
82
 * An error instead (and the same applies to all other requests, so this
83
 * won't be repeated) would provide both an error code and a more verbose
84
 * description of the cause of the issue:
85
 * 
86
\verbatim
87
{
88
        "recordplay" : "event",
89
        "error_code" : <numeric ID, check Macros below>,
90
        "error" : "<error description as a string>"
91
}
92
\endverbatim
93
 * 
94
 * The \c update request instead has to be formatted as follows:
95
 *
96
\verbatim
97
{
98
        "request" : "update"
99
}
100
\endverbatim
101
 *
102
 * which will always result in an immediate ack ( \c ok ):
103
 * 
104
\verbatim
105
{
106
        "recordplay" : "ok",
107
}
108
\endverbatim
109
 *
110
 * Coming to the asynchronous requests, \c record has to be attached to
111
 * a JSEP offer (failure to do so will result in an error) and has to be
112
 * formatted as follows:
113
 *
114
\verbatim
115
{
116
        "request" : "record",
117
        "name" : "<Pretty name for the recording>"
118
}
119
\endverbatim
120
 *
121
 * A successful management of this request will result in a \c recording
122
 * event which will include the unique ID of the recording and a JSEP
123
 * answer to complete the setup of the associated PeerConnection to record:
124
 * 
125
\verbatim
126
{
127
        "recordplay" : "event",
128
        "result": {
129
                "status" : "recording",
130
                "id" : <unique numeric ID>
131
        }
132
}
133
\endverbatim
134
 *
135
 * A \c stop request can interrupt the recording process and tear the
136
 * associated PeerConnection down:
137
 * 
138
\verbatim
139
{
140
        "request" : "stop",
141
}
142
\endverbatim
143
 * 
144
 * This will result in a \c stopped status:
145
 * 
146
\verbatim
147
{
148
        "recordplay" : "event",
149
        "result": {
150
                "status" : "stopped",
151
                "id" : <unique numeric ID of the interrupted recording>
152
        }
153
}
154
\endverbatim
155
 * 
156
 * For what concerns the playout, instead, the process is slightly
157
 * different: you first choose a recording to replay, using \c play ,
158
 * and then start its playout using a \c start request. Just as before,
159
 * a \c stop request will interrupt the playout and tear the PeerConnection
160
 * down. It's very important to point out that no JSEP offer must be
161
 * sent for replaying a recording: in this case, it will always be the
162
 * plugin to generate a JSON offer (in response to a \c play request),
163
 * which means you'll then have to provide a JSEP answer within the
164
 * context of the following \c start request which will close the circle.
165
 * 
166
 * A \c play request has to be formatted as follows:
167
 * 
168
\verbatim
169
{
170
        "request" : "play",
171
        "id" : <unique numeric ID of the recording to replay>
172
}
173
\endverbatim
174
 * 
175
 * This will result in a \c preparing status notification which will be
176
 * attached to the JSEP offer originated by the plugin in order to
177
 * match the media available in the recording:
178
 * 
179
\verbatim
180
{
181
        "recordplay" : "event",
182
        "result": {
183
                "status" : "preparing",
184
                "id" : <unique numeric ID of the recording>
185
        }
186
}
187
\endverbatim
188
 * 
189
 * A \c start request, which as anticipated must be attached to the JSEP
190
 * answer to the previous offer sent by the plugin, has to be formatted
191
 * as follows:
192
 * 
193
\verbatim
194
{
195
        "request" : "start",
196
}
197
\endverbatim
198
 * 
199
 * This will result in a \c playing status notification:
200
 * 
201
\verbatim
202
{
203
        "recordplay" : "event",
204
        "result": {
205
                "status" : "playing"
206
        }
207
}
208
\endverbatim
209
 * 
210
 * Just as before, a \c stop request can interrupt the playout process at
211
 * any time, and tear the associated PeerConnection down:
212
 * 
213
\verbatim
214
{
215
        "request" : "stop",
216
}
217
\endverbatim
218
 * 
219
 * This will result in a \c stopped status:
220
 * 
221
\verbatim
222
{
223
        "recordplay" : "event",
224
        "result": {
225
                "status" : "stopped"
226
        }
227
}
228
\endverbatim
229
 * 
230
 * If the plugin detects a loss of the associated PeerConnection, whether
231
 * as a result of a \c stop request or because the 10 seconds passed, a
232
 * \c done result notification is triggered to inform the application
233
 * the recording/playout session is over:
234
 * 
235
\verbatim
236
{
237
        "recordplay" : "event",
238
        "result": "done"
239
}
240
\endverbatim
241
 *
242
 * \ingroup plugins
243
 * \ref plugins
244
 */
245

    
246
#include "plugin.h"
247

    
248
#include <dirent.h>
249
#include <arpa/inet.h>
250
#include <sys/stat.h>
251
#include <sys/time.h>
252
#include <jansson.h>
253

    
254
#include "../debug.h"
255
#include "../apierror.h"
256
#include "../config.h"
257
#include "../mutex.h"
258
#include "../record.h"
259
#include "../rtp.h"
260
#include "../rtcp.h"
261
#include "../utils.h"
262

    
263

    
264
/* Plugin information */
265
#define JANUS_RECORDPLAY_VERSION                        3
266
#define JANUS_RECORDPLAY_VERSION_STRING                "0.0.3"
267
#define JANUS_RECORDPLAY_DESCRIPTION                "This is a trivial Record&Play plugin for Janus, to record WebRTC sessions and replay them."
268
#define JANUS_RECORDPLAY_NAME                                "JANUS Record&Play plugin"
269
#define JANUS_RECORDPLAY_AUTHOR                                "Meetecho s.r.l."
270
#define JANUS_RECORDPLAY_PACKAGE                        "janus.plugin.recordplay"
271

    
272
/* Plugin methods */
273
janus_plugin *create(void);
274
int janus_recordplay_init(janus_callbacks *callback, const char *onfig_path);
275
void janus_recordplay_destroy(void);
276
int janus_recordplay_get_api_compatibility(void);
277
int janus_recordplay_get_version(void);
278
const char *janus_recordplay_get_version_string(void);
279
const char *janus_recordplay_get_description(void);
280
const char *janus_recordplay_get_name(void);
281
const char *janus_recordplay_get_author(void);
282
const char *janus_recordplay_get_package(void);
283
void janus_recordplay_create_session(janus_plugin_session *handle, int *error);
284
struct janus_plugin_result *janus_recordplay_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
285
void janus_recordplay_setup_media(janus_plugin_session *handle);
286
void janus_recordplay_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
287
void janus_recordplay_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
288
void janus_recordplay_incoming_data(janus_plugin_session *handle, char *buf, int len);
289
void janus_recordplay_slow_link(janus_plugin_session *handle, int uplink, int video);
290
void janus_recordplay_hangup_media(janus_plugin_session *handle);
291
void janus_recordplay_destroy_session(janus_plugin_session *handle, int *error);
292
json_t *janus_recordplay_query_session(janus_plugin_session *handle);
293

    
294
/* Plugin setup */
295
static janus_plugin janus_recordplay_plugin =
296
        JANUS_PLUGIN_INIT (
297
                .init = janus_recordplay_init,
298
                .destroy = janus_recordplay_destroy,
299

    
300
                .get_api_compatibility = janus_recordplay_get_api_compatibility,
301
                .get_version = janus_recordplay_get_version,
302
                .get_version_string = janus_recordplay_get_version_string,
303
                .get_description = janus_recordplay_get_description,
304
                .get_name = janus_recordplay_get_name,
305
                .get_author = janus_recordplay_get_author,
306
                .get_package = janus_recordplay_get_package,
307
                
308
                .create_session = janus_recordplay_create_session,
309
                .handle_message = janus_recordplay_handle_message,
310
                .setup_media = janus_recordplay_setup_media,
311
                .incoming_rtp = janus_recordplay_incoming_rtp,
312
                .incoming_rtcp = janus_recordplay_incoming_rtcp,
313
                .incoming_data = janus_recordplay_incoming_data,
314
                .slow_link = janus_recordplay_slow_link,
315
                .hangup_media = janus_recordplay_hangup_media,
316
                .destroy_session = janus_recordplay_destroy_session,
317
                .query_session = janus_recordplay_query_session,
318
        );
319

    
320
/* Plugin creator */
321
janus_plugin *create(void) {
322
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_RECORDPLAY_NAME);
323
        return &janus_recordplay_plugin;
324
}
325

    
326
/* Parameter validation */
327
static struct janus_json_parameter request_parameters[] = {
328
        {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
329
};
330
static struct janus_json_parameter configure_parameters[] = {
331
        {"video-bitrate-max", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
332
        {"video-keyframe-interval", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
333
};
334
static struct janus_json_parameter record_parameters[] = {
335
        {"name", JSON_STRING, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_NONEMPTY},
336
        {"filename", JSON_STRING, 0}
337
};
338
static struct janus_json_parameter play_parameters[] = {
339
        {"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
340
};
341

    
342
/* Useful stuff */
343
static volatile gint initialized = 0, stopping = 0;
344
static gboolean notify_events = TRUE;
345
static janus_callbacks *gateway = NULL;
346
static GThread *handler_thread;
347
static GThread *watchdog;
348
static void *janus_recordplay_handler(void *data);
349

    
350
typedef struct janus_recordplay_message {
351
        janus_plugin_session *handle;
352
        char *transaction;
353
        json_t *message;
354
        json_t *jsep;
355
} janus_recordplay_message;
356
static GAsyncQueue *messages = NULL;
357
static janus_recordplay_message exit_message;
358

    
359
typedef struct janus_recordplay_rtp_header_extension {
360
        uint16_t type;
361
        uint16_t length;
362
} janus_recordplay_rtp_header_extension;
363

    
364
typedef struct janus_recordplay_frame_packet {
365
        uint16_t seq;        /* RTP Sequence number */
366
        uint64_t ts;        /* RTP Timestamp */
367
        int len;                /* Length of the data */
368
        long offset;        /* Offset of the data in the file */
369
        struct janus_recordplay_frame_packet *next;
370
        struct janus_recordplay_frame_packet *prev;
371
} janus_recordplay_frame_packet;
372
janus_recordplay_frame_packet *janus_recordplay_get_frames(const char *dir, const char *filename);
373

    
374
typedef struct janus_recordplay_recording {
375
        guint64 id;                        /* Recording unique ID */
376
        char *name;                        /* Name of the recording */
377
        char *date;                        /* Time of the recording */
378
        char *arc_file;                /* Audio file name */
379
        char *vrc_file;                /* Video file name */
380
        gboolean completed;        /* Whether this recording was completed or still going on */
381
        GList *viewers;                /* List of users watching this recording */
382
        gint64 destroyed;        /* Lazy timestamp to mark recordings as destroyed */
383
        janus_mutex mutex;        /* Mutex for this recording */
384
} janus_recordplay_recording;
385
static GHashTable *recordings = NULL;
386
static janus_mutex recordings_mutex;
387

    
388
typedef struct janus_recordplay_session {
389
        janus_plugin_session *handle;
390
        gboolean active;
391
        gboolean recorder;                /* Whether this session is used to record or to replay a WebRTC session */
392
        gboolean firefox;        /* We send Firefox users a different kind of FIR */
393
        janus_recordplay_recording *recording;
394
        janus_recorder *arc;        /* Audio recorder */
395
        janus_recorder *vrc;        /* Video recorder */
396
        janus_mutex rec_mutex;        /* Mutex to protect the recorders from race conditions */
397
        janus_recordplay_frame_packet *aframes;        /* Audio frames (for playout) */
398
        janus_recordplay_frame_packet *vframes;        /* Video frames (for playout) */
399
        guint video_remb_startup;
400
        guint64 video_remb_last;
401
        guint64 video_bitrate;
402
        guint video_keyframe_interval; /* keyframe request interval (ms) */
403
        guint64 video_keyframe_request_last; /* timestamp of last keyframe request sent */
404
        gint video_fir_seq;
405
        volatile gint hangingup;
406
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
407
} janus_recordplay_session;
408
static GHashTable *sessions;
409
static GList *old_sessions;
410
static janus_mutex sessions_mutex;
411

    
412

    
413
static char *recordings_path = NULL;
414
void janus_recordplay_update_recordings_list(void);
415
static void *janus_recordplay_playout_thread(void *data);
416

    
417
/* Helper to send RTCP feedback back to recorders, if needed */
418
void janus_recordplay_send_rtcp_feedback(janus_plugin_session *handle, int video, char *buf, int len);
419

    
420

    
421
/* SDP offer/answer templates for the playout */
422
#define OPUS_PT                111
423
#define VP8_PT                100
424
#define sdp_template \
425
                "v=0\r\n" \
426
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */ \
427
                "s=%s\r\n"                                                        /* Recording playout id */ \
428
                "t=0 0\r\n" \
429
                "%s%s"                                                                /* Audio and/or video m-lines */
430
#define sdp_a_template \
431
                "m=audio 1 RTP/SAVPF %d\r\n"                /* Opus payload type */ \
432
                "c=IN IP4 1.1.1.1\r\n" \
433
                "a=%s\r\n"                                                        /* Media direction */ \
434
                "a=rtpmap:%d opus/48000/2\r\n"                /* Opus payload type */
435
#define sdp_v_template \
436
                "m=video 1 RTP/SAVPF %d\r\n"                /* VP8 payload type */ \
437
                "c=IN IP4 1.1.1.1\r\n" \
438
                "a=%s\r\n"                                                        /* Media direction */ \
439
                "a=rtpmap:%d VP8/90000\r\n"                        /* VP8 payload type */ \
440
                "a=rtcp-fb:%d ccm fir\r\n"                        /* VP8 payload type */ \
441
                "a=rtcp-fb:%d nack\r\n"                                /* VP8 payload type */ \
442
                "a=rtcp-fb:%d nack pli\r\n"                        /* VP8 payload type */ \
443
                "a=rtcp-fb:%d goog-remb\r\n"                /* VP8 payload type */
444

    
445

    
446
static void janus_recordplay_message_free(janus_recordplay_message *msg) {
447
        if(!msg || msg == &exit_message)
448
                return;
449

    
450
        msg->handle = NULL;
451

    
452
        g_free(msg->transaction);
453
        msg->transaction = NULL;
454
        if(msg->message)
455
                json_decref(msg->message);
456
        msg->message = NULL;
457
        if(msg->jsep)
458
                json_decref(msg->jsep);
459
        msg->jsep = NULL;
460

    
461
        g_free(msg);
462
}
463

    
464

    
465
/* Error codes */
466
#define JANUS_RECORDPLAY_ERROR_NO_MESSAGE                        411
467
#define JANUS_RECORDPLAY_ERROR_INVALID_JSON                412
468
#define JANUS_RECORDPLAY_ERROR_INVALID_REQUEST        413
469
#define JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT        414
470
#define JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT        415
471
#define JANUS_RECORDPLAY_ERROR_NOT_FOUND        416
472
#define JANUS_RECORDPLAY_ERROR_INVALID_RECORDING        417
473
#define JANUS_RECORDPLAY_ERROR_INVALID_STATE        418
474
#define JANUS_RECORDPLAY_ERROR_UNKNOWN_ERROR        499
475

    
476

    
477
/* Record&Play watchdog/garbage collector (sort of) */
478
void *janus_recordplay_watchdog(void *data);
479
void *janus_recordplay_watchdog(void *data) {
480
        JANUS_LOG(LOG_INFO, "Record&Play watchdog started\n");
481
        gint64 now = 0;
482
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
483
                janus_mutex_lock(&sessions_mutex);
484
                /* Iterate on all the sessions */
485
                now = janus_get_monotonic_time();
486
                if(old_sessions != NULL) {
487
                        GList *sl = old_sessions;
488
                        JANUS_LOG(LOG_HUGE, "Checking %d old Record&Play sessions...\n", g_list_length(old_sessions));
489
                        while(sl) {
490
                                janus_recordplay_session *session = (janus_recordplay_session *)sl->data;
491
                                if(!session) {
492
                                        sl = sl->next;
493
                                        continue;
494
                                }
495
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
496
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
497
                                        JANUS_LOG(LOG_VERB, "Freeing old Record&Play session\n");
498
                                        GList *rm = sl->next;
499
                                        old_sessions = g_list_delete_link(old_sessions, sl);
500
                                        sl = rm;
501
                                        session->handle = NULL;
502
                                        g_free(session);
503
                                        session = NULL;
504
                                        continue;
505
                                }
506
                                sl = sl->next;
507
                        }
508
                }
509
                janus_mutex_unlock(&sessions_mutex);
510
                g_usleep(500000);
511
        }
512
        JANUS_LOG(LOG_INFO, "Record&Play watchdog stopped\n");
513
        return NULL;
514
}
515

    
516

    
517
/* Plugin implementation */
518
int janus_recordplay_init(janus_callbacks *callback, const char *config_path) {
519
        if(g_atomic_int_get(&stopping)) {
520
                /* Still stopping from before */
521
                return -1;
522
        }
523
        if(callback == NULL || config_path == NULL) {
524
                /* Invalid arguments */
525
                return -1;
526
        }
527

    
528
        /* Read configuration */
529
        char filename[255];
530
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_RECORDPLAY_PACKAGE);
531
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
532
        janus_config *config = janus_config_parse(filename);
533
        if(config != NULL)
534
                janus_config_print(config);
535
        /* Parse configuration */
536
        if(config != NULL) {
537
                janus_config_item *path = janus_config_get_item_drilldown(config, "general", "path");
538
                if(path && path->value)
539
                        recordings_path = g_strdup(path->value);
540
                janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events");
541
                if(events != NULL && events->value != NULL)
542
                        notify_events = janus_is_true(events->value);
543
                if(!notify_events && callback->events_is_enabled()) {
544
                        JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_RECORDPLAY_NAME);
545
                }
546
                /* Done */
547
                janus_config_destroy(config);
548
                config = NULL;
549
        }
550
        if(recordings_path == NULL) {
551
                recordings_path = g_strdup("/tmp");
552
                JANUS_LOG(LOG_WARN, "No recordings path specified, using /tmp...\n");
553
        }
554
        /* Create the folder, if needed */
555
        struct stat st = {0};
556
        if(stat(recordings_path, &st) == -1) {
557
                int res = janus_mkdir(recordings_path, 0755);
558
                JANUS_LOG(LOG_VERB, "Creating folder: %d\n", res);
559
                if(res != 0) {
560
                        JANUS_LOG(LOG_ERR, "%s", strerror(res));
561
                        return -1;        /* No point going on... */
562
                }
563
        }
564
        recordings = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
565
        janus_mutex_init(&recordings_mutex);
566
        janus_recordplay_update_recordings_list();
567
        
568
        sessions = g_hash_table_new(NULL, NULL);
569
        janus_mutex_init(&sessions_mutex);
570
        messages = g_async_queue_new_full((GDestroyNotify) janus_recordplay_message_free);
571
        /* This is the callback we'll need to invoke to contact the gateway */
572
        gateway = callback;
573

    
574
        g_atomic_int_set(&initialized, 1);
575

    
576
        GError *error = NULL;
577
        /* Start the sessions watchdog */
578
        watchdog = g_thread_try_new("recordplay watchdog", &janus_recordplay_watchdog, NULL, &error);
579
        if(error != NULL) {
580
                g_atomic_int_set(&initialized, 0);
581
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Record&Play watchdog thread...\n", error->code, error->message ? error->message : "??");
582
                return -1;
583
        }
584
        /* Launch the thread that will handle incoming messages */
585
        handler_thread = g_thread_try_new("recordplay handler", janus_recordplay_handler, NULL, &error);
586
        if(error != NULL) {
587
                g_atomic_int_set(&initialized, 0);
588
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Record&Play handler thread...\n", error->code, error->message ? error->message : "??");
589
                return -1;
590
        }
591
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_RECORDPLAY_NAME);
592
        return 0;
593
}
594

    
595
void janus_recordplay_destroy(void) {
596
        if(!g_atomic_int_get(&initialized))
597
                return;
598
        g_atomic_int_set(&stopping, 1);
599

    
600
        g_async_queue_push(messages, &exit_message);
601
        if(handler_thread != NULL) {
602
                g_thread_join(handler_thread);
603
                handler_thread = NULL;
604
        }
605
        if(watchdog != NULL) {
606
                g_thread_join(watchdog);
607
                watchdog = NULL;
608
        }
609
        /* FIXME We should destroy the sessions cleanly */
610
        janus_mutex_lock(&sessions_mutex);
611
        g_hash_table_destroy(sessions);
612
        janus_mutex_unlock(&sessions_mutex);
613
        g_async_queue_unref(messages);
614
        messages = NULL;
615
        sessions = NULL;
616
        g_atomic_int_set(&initialized, 0);
617
        g_atomic_int_set(&stopping, 0);
618
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_RECORDPLAY_NAME);
619
}
620

    
621
int janus_recordplay_get_api_compatibility(void) {
622
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
623
        return JANUS_PLUGIN_API_VERSION;
624
}
625

    
626
int janus_recordplay_get_version(void) {
627
        return JANUS_RECORDPLAY_VERSION;
628
}
629

    
630
const char *janus_recordplay_get_version_string(void) {
631
        return JANUS_RECORDPLAY_VERSION_STRING;
632
}
633

    
634
const char *janus_recordplay_get_description(void) {
635
        return JANUS_RECORDPLAY_DESCRIPTION;
636
}
637

    
638
const char *janus_recordplay_get_name(void) {
639
        return JANUS_RECORDPLAY_NAME;
640
}
641

    
642
const char *janus_recordplay_get_author(void) {
643
        return JANUS_RECORDPLAY_AUTHOR;
644
}
645

    
646
const char *janus_recordplay_get_package(void) {
647
        return JANUS_RECORDPLAY_PACKAGE;
648
}
649

    
650
void janus_recordplay_create_session(janus_plugin_session *handle, int *error) {
651
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
652
                *error = -1;
653
                return;
654
        }        
655
        janus_recordplay_session *session = (janus_recordplay_session *)g_malloc0(sizeof(janus_recordplay_session));
656
        session->handle = handle;
657
        session->active = FALSE;
658
        session->recorder = FALSE;
659
        session->firefox = FALSE;
660
        session->arc = NULL;
661
        session->vrc = NULL;
662
        janus_mutex_init(&session->rec_mutex);
663
        session->destroyed = 0;
664
        g_atomic_int_set(&session->hangingup, 0);
665
        session->video_remb_startup = 4;
666
        session->video_remb_last = janus_get_monotonic_time();
667
        session->video_bitrate = 1024 * 1024;                 /* This is 1mbps by default */
668
        session->video_keyframe_request_last = 0;
669
        session->video_keyframe_interval = 15000;         /* 15 seconds by default */
670
        session->video_fir_seq = 0;
671
        handle->plugin_handle = session;
672
        janus_mutex_lock(&sessions_mutex);
673
        g_hash_table_insert(sessions, handle, session);
674
        janus_mutex_unlock(&sessions_mutex);
675

    
676
        return;
677
}
678

    
679
void janus_recordplay_destroy_session(janus_plugin_session *handle, int *error) {
680
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
681
                *error = -1;
682
                return;
683
        }        
684
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;
685
        if(!session) {
686
                JANUS_LOG(LOG_ERR, "No Record&Play session associated with this handle...\n");
687
                *error = -2;
688
                return;
689
        }
690
        janus_mutex_lock(&sessions_mutex);
691
        if(!session->destroyed) {
692
                JANUS_LOG(LOG_VERB, "Removing Record&Play session...\n");
693
                janus_recordplay_hangup_media(handle);
694
                session->destroyed = janus_get_monotonic_time();
695
                g_hash_table_remove(sessions, handle);
696
                /* Cleaning up and removing the session is done in a lazy way */
697
                old_sessions = g_list_append(old_sessions, session);
698
        }
699
        janus_mutex_unlock(&sessions_mutex);
700
        return;
701
}
702

    
703
json_t *janus_recordplay_query_session(janus_plugin_session *handle) {
704
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
705
                return NULL;
706
        }        
707
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;
708
        if(!session) {
709
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
710
                return NULL;
711
        }
712
        /* In the echo test, every session is the same: we just provide some configure info */
713
        json_t *info = json_object();
714
        json_object_set_new(info, "type", json_string(session->recorder ? "recorder" : (session->recording ? "player" : "none")));
715
        if(session->recording) {
716
                json_object_set_new(info, "recording_id", json_integer(session->recording->id));
717
                json_object_set_new(info, "recording_name", json_string(session->recording->name));
718
        }
719
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
720
        return info;
721
}
722

    
723
struct janus_plugin_result *janus_recordplay_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
724
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
725
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
726

    
727
        /* Pre-parse the message */
728
        int error_code = 0;
729
        char error_cause[512];
730
        json_t *root = message;
731
        json_t *response = NULL;
732
        
733
        if(message == NULL) {
734
                JANUS_LOG(LOG_ERR, "No message??\n");
735
                error_code = JANUS_RECORDPLAY_ERROR_NO_MESSAGE;
736
                g_snprintf(error_cause, 512, "%s", "No message??");
737
                goto plugin_response;
738
        }
739

    
740
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;        
741
        if(!session) {
742
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
743
                error_code = JANUS_RECORDPLAY_ERROR_UNKNOWN_ERROR;
744
                g_snprintf(error_cause, 512, "%s", "session associated with this handle...");
745
                goto plugin_response;
746
        }
747
        if(session->destroyed) {
748
                JANUS_LOG(LOG_ERR, "Session has already been destroyed...\n");
749
                error_code = JANUS_RECORDPLAY_ERROR_UNKNOWN_ERROR;
750
                g_snprintf(error_cause, 512, "%s", "Session has already been destroyed...");
751
                goto plugin_response;
752
        }
753
        if(!json_is_object(root)) {
754
                JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
755
                error_code = JANUS_RECORDPLAY_ERROR_INVALID_JSON;
756
                g_snprintf(error_cause, 512, "JSON error: not an object");
757
                goto plugin_response;
758
        }
759
        /* Get the request first */
760
        JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
761
                error_code, error_cause, TRUE,
762
                JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT, JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT);
763
        if(error_code != 0)
764
                goto plugin_response;
765
        json_t *request = json_object_get(root, "request");
766
        /* Some requests ('create' and 'destroy') can be handled synchronously */
767
        const char *request_text = json_string_value(request);
768
        if(!strcasecmp(request_text, "update")) {
769
                /* Update list of available recordings, scanning the folder again */
770
                janus_recordplay_update_recordings_list();
771
                /* Send info back */
772
                response = json_object();
773
                json_object_set_new(response, "recordplay", json_string("ok"));
774
                goto plugin_response;
775
        } else if(!strcasecmp(request_text, "list")) {
776
                json_t *list = json_array();
777
                JANUS_LOG(LOG_VERB, "Request for the list of recordings\n");
778
                /* Return a list of all available recordings */
779
                janus_mutex_lock(&recordings_mutex);
780
                GHashTableIter iter;
781
                gpointer value;
782
                g_hash_table_iter_init(&iter, recordings);
783
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
784
                        janus_recordplay_recording *rec = value;
785
                        if(!rec->completed)        /* Ongoing recording, skip */
786
                                continue;
787
                        json_t *ml = json_object();
788
                        json_object_set_new(ml, "id", json_integer(rec->id));
789
                        json_object_set_new(ml, "name", json_string(rec->name));
790
                        json_object_set_new(ml, "date", json_string(rec->date));
791
                        json_object_set_new(ml, "audio", rec->arc_file ? json_true() : json_false());
792
                        json_object_set_new(ml, "video", rec->vrc_file ? json_true() : json_false());
793
                        json_array_append_new(list, ml);
794
                }
795
                janus_mutex_unlock(&recordings_mutex);
796
                /* Send info back */
797
                response = json_object();
798
                json_object_set_new(response, "recordplay", json_string("list"));
799
                json_object_set_new(response, "list", list);
800
                goto plugin_response;
801
        } else if(!strcasecmp(request_text, "configure")) {
802
                JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
803
                        error_code, error_cause, TRUE,
804
                        JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT, JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT);
805
                if(error_code != 0)
806
                        goto plugin_response;
807
                json_t *video_bitrate_max = json_object_get(root, "video-bitrate-max");
808
                if(video_bitrate_max) {
809
                        session->video_bitrate = json_integer_value(video_bitrate_max);
810
                        JANUS_LOG(LOG_VERB, "Video bitrate has been set to %"SCNu64"\n", session->video_bitrate);
811
                }
812
                json_t *video_keyframe_interval= json_object_get(root, "video-keyframe-interval");
813
                if(video_keyframe_interval) {
814
                        session->video_keyframe_interval = json_integer_value(video_keyframe_interval);
815
                        JANUS_LOG(LOG_VERB, "Video keyframe interval has been set to %u\n", session->video_keyframe_interval);
816
                }
817
                response = json_object();
818
                json_object_set_new(response, "recordplay", json_string("configure"));
819
                json_object_set_new(response, "status", json_string("ok"));
820
                /* Return a success, and also let the client be aware of what changed, to allow crosschecks */
821
                json_t *settings = json_object();
822
                json_object_set_new(settings, "video-keyframe-interval", json_integer(session->video_keyframe_interval)); 
823
                json_object_set_new(settings, "video-bitrate-max", json_integer(session->video_bitrate)); 
824
                json_object_set_new(response, "settings", settings); 
825
                goto plugin_response;
826
        } else if(!strcasecmp(request_text, "record") || !strcasecmp(request_text, "play")
827
                        || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "stop")) {
828
                /* These messages are handled asynchronously */
829
                janus_recordplay_message *msg = g_malloc0(sizeof(janus_recordplay_message));
830
                msg->handle = handle;
831
                msg->transaction = transaction;
832
                msg->message = root;
833
                msg->jsep = jsep;
834

    
835
                g_async_queue_push(messages, msg);
836

    
837
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
838
        } else {
839
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
840
                error_code = JANUS_RECORDPLAY_ERROR_INVALID_REQUEST;
841
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
842
        }
843

    
844
plugin_response:
845
                {
846
                        if(error_code == 0 && !response) {
847
                                error_code = JANUS_RECORDPLAY_ERROR_UNKNOWN_ERROR;
848
                                g_snprintf(error_cause, 512, "Invalid response");
849
                        }
850
                        if(error_code != 0) {
851
                                /* Prepare JSON error event */
852
                                json_t *event = json_object();
853
                                json_object_set_new(event, "recordplay", json_string("event"));
854
                                json_object_set_new(event, "error_code", json_integer(error_code));
855
                                json_object_set_new(event, "error", json_string(error_cause));
856
                                response = event;
857
                        }
858
                        if(root != NULL)
859
                                json_decref(root);
860
                        if(jsep != NULL)
861
                                json_decref(jsep);
862
                        g_free(transaction);
863

    
864
                        return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
865
                }
866

    
867
}
868

    
869
void janus_recordplay_setup_media(janus_plugin_session *handle) {
870
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
871
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
872
                return;
873
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;        
874
        if(!session) {
875
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
876
                return;
877
        }
878
        if(session->destroyed)
879
                return;
880
        g_atomic_int_set(&session->hangingup, 0);
881
        /* Take note of the fact that the session is now active */
882
        session->active = TRUE;
883
        if(!session->recorder) {
884
                GError *error = NULL;
885
                g_thread_try_new("recordplay playout thread", &janus_recordplay_playout_thread, session, &error);
886
                if(error != NULL) {
887
                        /* FIXME Should we notify this back to the user somehow? */
888
                        JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Record&Play playout thread...\n", error->code, error->message ? error->message : "??");
889
                }
890
        }
891
}
892

    
893
void janus_recordplay_send_rtcp_feedback(janus_plugin_session *handle, int video, char *buf, int len) {
894
        if(video != 1)
895
                return;        /* We just do this for video, for now */
896

    
897
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;
898
        char rtcpbuf[24];
899

    
900
        /* Send a RR+SDES+REMB every five seconds, or ASAP while we are still
901
         * ramping up (first 4 RTP packets) */
902
        gint64 now = janus_get_monotonic_time();
903
        guint64 elapsed = now - session->video_remb_last;
904
        gboolean remb_rampup = session->video_remb_startup > 0;
905

    
906
        if(remb_rampup || (elapsed >= 5*G_USEC_PER_SEC)) {
907
                guint64 bitrate = session->video_bitrate;
908

    
909
                if(remb_rampup) {
910
                        bitrate = bitrate / session->video_remb_startup;
911
                        session->video_remb_startup--;
912
                }
913

    
914
                /* Send a new REMB back */
915
                char rtcpbuf[24];
916
                janus_rtcp_remb((char *)(&rtcpbuf), 24, bitrate);
917
                gateway->relay_rtcp(handle, video, rtcpbuf, 24);
918

    
919
                session->video_remb_last = now;
920
        }
921

    
922
        /* Request a keyframe on a regular basis (every session->video_keyframe_interval ms) */
923
        elapsed = now - session->video_keyframe_request_last;
924
        guint64 interval = (session->video_keyframe_interval / 1000) * G_USEC_PER_SEC;
925

    
926
        if(elapsed >= interval) {
927
                /* Send both a FIR and a PLI, just to be sure */
928
                memset(rtcpbuf, 0, 20);
929
                janus_rtcp_fir((char *)&rtcpbuf, 20, &session->video_fir_seq);
930
                gateway->relay_rtcp(handle, video, rtcpbuf, 20);
931
                memset(rtcpbuf, 0, 12);
932
                janus_rtcp_pli((char *)&rtcpbuf, 12);
933
                gateway->relay_rtcp(handle, video, rtcpbuf, 12);
934
                session->video_keyframe_request_last = now;
935
        }
936
}
937

    
938
void janus_recordplay_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
939
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
940
                return;
941
        if(gateway) {
942
                janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;        
943
                if(!session) {
944
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
945
                        return;
946
                }
947
                if(session->destroyed)
948
                        return;
949
                /* Are we recording? */
950
                if(session->recorder) {
951
                        janus_recorder_save_frame(video ? session->vrc : session->arc, buf, len);
952
                }
953

    
954
                janus_recordplay_send_rtcp_feedback(handle, video, buf, len);
955
        }
956
}
957

    
958
void janus_recordplay_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
959
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
960
                return;
961
}
962

    
963
void janus_recordplay_incoming_data(janus_plugin_session *handle, char *buf, int len) {
964
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
965
                return;
966
        /* FIXME We don't care */
967
}
968

    
969
void janus_recordplay_slow_link(janus_plugin_session *handle, int uplink, int video) {
970
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
971
                return;
972

    
973
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;
974
        if(!session || session->destroyed)
975
                return;
976

    
977
        json_t *event = json_object();
978
        json_object_set_new(event, "recordplay", json_string("event"));
979
        json_t *result = json_object();
980
        json_object_set_new(result, "status", json_string("slow_link"));
981
        /* What is uplink for the server is downlink for the client, so turn the tables */
982
        json_object_set_new(result, "current-bitrate", json_integer(session->video_bitrate));
983
        json_object_set_new(result, "uplink", json_integer(uplink ? 0 : 1));
984
        json_object_set_new(event, "result", result);
985
        gateway->push_event(session->handle, &janus_recordplay_plugin, NULL, event, NULL);
986
        json_decref(event);
987
}
988

    
989
void janus_recordplay_hangup_media(janus_plugin_session *handle) {
990
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
991
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
992
                return;
993
        janus_recordplay_session *session = (janus_recordplay_session *)handle->plugin_handle;
994
        if(!session) {
995
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
996
                return;
997
        }
998
        session->active = FALSE;
999
        if(session->destroyed || !session->recorder)
1000
                return;
1001
        if(g_atomic_int_add(&session->hangingup, 1))
1002
                return;
1003

    
1004
        /* Send an event to the browser and tell it's over */
1005
        json_t *event = json_object();
1006
        json_object_set_new(event, "recordplay", json_string("event"));
1007
        json_object_set_new(event, "result", json_string("done"));
1008
        int ret = gateway->push_event(handle, &janus_recordplay_plugin, NULL, event, NULL);
1009
        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
1010
        json_decref(event);
1011

    
1012
        /* FIXME Simulate a "stop" coming from the browser */
1013
        janus_recordplay_message *msg = g_malloc0(sizeof(janus_recordplay_message));
1014
        msg->handle = handle;
1015
        msg->message = json_pack("{ss}", "request", "stop");
1016
        msg->transaction = NULL;
1017
        msg->jsep = NULL;
1018
        g_async_queue_push(messages, msg);
1019
}
1020

    
1021
/* Thread to handle incoming messages */
1022
static void *janus_recordplay_handler(void *data) {
1023
        JANUS_LOG(LOG_VERB, "Joining Record&Play handler thread\n");
1024
        janus_recordplay_message *msg = NULL;
1025
        int error_code = 0;
1026
        char error_cause[512];
1027
        json_t *root = NULL;
1028
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
1029
                msg = g_async_queue_pop(messages);
1030
                if(msg == NULL)
1031
                        continue;
1032
                if(msg == &exit_message)
1033
                        break;
1034
                if(msg->handle == NULL) {
1035
                        janus_recordplay_message_free(msg);
1036
                        continue;
1037
                }
1038
                janus_recordplay_session *session = NULL;
1039
                janus_mutex_lock(&sessions_mutex);
1040
                if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
1041
                        session = (janus_recordplay_session *)msg->handle->plugin_handle;
1042
                }
1043
                janus_mutex_unlock(&sessions_mutex);
1044
                if(!session) {
1045
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1046
                        janus_recordplay_message_free(msg);
1047
                        continue;
1048
                }
1049
                if(session->destroyed) {
1050
                        janus_recordplay_message_free(msg);
1051
                        continue;
1052
                }
1053
                /* Handle request */
1054
                error_code = 0;
1055
                root = NULL;
1056
                if(msg->message == NULL) {
1057
                        JANUS_LOG(LOG_ERR, "No message??\n");
1058
                        error_code = JANUS_RECORDPLAY_ERROR_NO_MESSAGE;
1059
                        g_snprintf(error_cause, 512, "%s", "No message??");
1060
                        goto error;
1061
                }
1062
                root = msg->message;
1063
                /* Get the request first */
1064
                JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
1065
                        error_code, error_cause, TRUE,
1066
                        JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT, JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT);
1067
                if(error_code != 0)
1068
                        goto error;
1069
                const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
1070
                const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
1071
                json_t *request = json_object_get(root, "request");
1072
                const char *request_text = json_string_value(request);
1073
                json_t *event = NULL;
1074
                json_t *result = NULL;
1075
                char *sdp = NULL;
1076
                const char *filename_text = NULL;
1077
                if(!strcasecmp(request_text, "record")) {
1078
                        if(!msg_sdp) {
1079
                                JANUS_LOG(LOG_ERR, "Missing SDP offer\n");
1080
                                error_code = JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT;
1081
                                g_snprintf(error_cause, 512, "Missing SDP offer");
1082
                                goto error;
1083
                        }
1084
                        JANUS_VALIDATE_JSON_OBJECT(root, record_parameters,
1085
                                error_code, error_cause, TRUE,
1086
                                JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT, JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT);
1087
                        if(error_code != 0)
1088
                                goto error;
1089
                        json_t *name = json_object_get(root, "name");
1090
                        const char *name_text = json_string_value(name);
1091
                        json_t *filename = json_object_get(root, "filename");
1092
                        if(filename) {
1093
                                filename_text = json_string_value(filename);
1094
                        }
1095
                        guint64 id = 0;
1096
                        while(id == 0) {
1097
                                id = janus_random_uint64();
1098
                                if(g_hash_table_lookup(recordings, &id) != NULL) {
1099
                                        /* Room ID already taken, try another one */
1100
                                        id = 0;
1101
                                }
1102
                        }
1103
                        JANUS_LOG(LOG_VERB, "Starting new recording with ID %"SCNu64"\n", id);
1104
                        janus_recordplay_recording *rec = (janus_recordplay_recording *)g_malloc0(sizeof(janus_recordplay_recording));
1105
                        rec->id = id;
1106
                        rec->name = g_strdup(name_text);
1107
                        rec->viewers = NULL;
1108
                        rec->destroyed = 0;
1109
                        rec->completed = FALSE;
1110
                        janus_mutex_init(&rec->mutex);
1111
                        /* Create a date string */
1112
                        time_t t = time(NULL);
1113
                        struct tm *tmv = localtime(&t);
1114
                        char outstr[200];
1115
                        strftime(outstr, sizeof(outstr), "%Y-%m-%d %H:%M:%S", tmv);
1116
                        rec->date = g_strdup(outstr);
1117
                        if(strstr(msg_sdp, "m=audio")) {
1118
                                char filename[256];
1119
                                if(filename_text != NULL) {
1120
                                        g_snprintf(filename, 256, "%s-audio", filename_text);
1121
                                } else {
1122
                                        g_snprintf(filename, 256, "rec-%"SCNu64"-audio", id);
1123
                                }
1124
                                rec->arc_file = g_strdup(filename);
1125
                                /* FIXME Assuming Opus */
1126
                                session->arc = janus_recorder_create(recordings_path, "opus", rec->arc_file);
1127
                        }
1128
                        if(strstr(msg_sdp, "m=video")) {
1129
                                char filename[256];
1130
                                if(filename_text != NULL) {
1131
                                        g_snprintf(filename, 256, "%s-video", filename_text);
1132
                                } else {
1133
                                        g_snprintf(filename, 256, "rec-%"SCNu64"-video", id);
1134
                                }
1135
                                rec->vrc_file = g_strdup(filename);
1136
                                /* FIXME Assuming VP8 */
1137
                                session->vrc = janus_recorder_create(recordings_path, "vp8", rec->vrc_file);
1138
                        }
1139
                        session->recorder = TRUE;
1140
                        session->recording = rec;
1141
                        janus_mutex_lock(&recordings_mutex);
1142
                        g_hash_table_insert(recordings, janus_uint64_dup(rec->id), rec);
1143
                        janus_mutex_unlock(&recordings_mutex);
1144
                        /* We need to prepare an answer */
1145
                        int opus_pt = 0, vp8_pt = 0;
1146
                        opus_pt = janus_get_codec_pt(msg_sdp, "opus");
1147
                        JANUS_LOG(LOG_VERB, "Opus payload type is %d\n", opus_pt);
1148
                        vp8_pt = janus_get_codec_pt(msg_sdp, "vp8");
1149
                        JANUS_LOG(LOG_VERB, "VP8 payload type is %d\n", vp8_pt);
1150
                        char sdptemp[1024], audio_mline[256], video_mline[512];
1151
                        if(opus_pt > 0) {
1152
                                g_snprintf(audio_mline, 256, sdp_a_template,
1153
                                        opus_pt,                                                /* Opus payload type */
1154
                                        "recvonly",                                                /* Recording is recvonly */
1155
                                        opus_pt);                                                 /* Opus payload type */
1156
                        } else {
1157
                                audio_mline[0] = '\0';
1158
                        }
1159
                        if(vp8_pt > 0) {
1160
                                g_snprintf(video_mline, 512, sdp_v_template,
1161
                                        vp8_pt,                                                        /* VP8 payload type */
1162
                                        "recvonly",                                                /* Recording is recvonly */
1163
                                        vp8_pt,                                                 /* VP8 payload type */
1164
                                        vp8_pt,                                                 /* VP8 payload type */
1165
                                        vp8_pt,                                                 /* VP8 payload type */
1166
                                        vp8_pt,                                                 /* VP8 payload type */
1167
                                        vp8_pt);                                                 /* VP8 payload type */
1168
                        } else {
1169
                                video_mline[0] = '\0';
1170
                        }
1171
                        g_snprintf(sdptemp, 1024, sdp_template,
1172
                                janus_get_real_time(),                        /* We need current time here */
1173
                                janus_get_real_time(),                        /* We need current time here */
1174
                                session->recording->name,                /* Playout session */
1175
                                audio_mline,                                        /* Audio m-line, if any */
1176
                                video_mline);                                        /* Video m-line, if any */
1177
                        sdp = g_strdup(sdptemp);
1178
                        JANUS_LOG(LOG_VERB, "Going to answer this SDP:\n%s\n", sdp);
1179
                        /* Done! */
1180
                        result = json_object();
1181
                        json_object_set_new(result, "status", json_string("recording"));
1182
                        json_object_set_new(result, "id", json_integer(id));
1183
                        /* Also notify event handlers */
1184
                        if(notify_events && gateway->events_is_enabled()) {
1185
                                json_t *info = json_object();
1186
                                json_object_set_new(info, "event", json_string("recording"));
1187
                                json_object_set_new(info, "id", json_integer(id));
1188
                                json_object_set_new(info, "audio", session->arc ? json_true() : json_false());
1189
                                json_object_set_new(info, "video", session->vrc ? json_true() : json_false());
1190
                                gateway->notify_event(&janus_recordplay_plugin, session->handle, info);
1191
                        }
1192
                } else if(!strcasecmp(request_text, "play")) {
1193
                        if(msg_sdp) {
1194
                                JANUS_LOG(LOG_ERR, "A play request can't contain an SDP\n");
1195
                                error_code = JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT;
1196
                                g_snprintf(error_cause, 512, "A play request can't contain an SDP");
1197
                                goto error;
1198
                        }
1199
                        JANUS_LOG(LOG_VERB, "Replaying a recording\n");
1200
                        JANUS_VALIDATE_JSON_OBJECT(root, play_parameters,
1201
                                error_code, error_cause, TRUE,
1202
                                JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT, JANUS_RECORDPLAY_ERROR_INVALID_ELEMENT);
1203
                        if(error_code != 0)
1204
                                goto error;
1205
                        json_t *id = json_object_get(root, "id");
1206
                        guint64 id_value = json_integer_value(id);
1207
                        /* Look for this recording */
1208
                        janus_mutex_lock(&recordings_mutex);
1209
                        janus_recordplay_recording *rec = g_hash_table_lookup(recordings, &id_value);
1210
                        janus_mutex_unlock(&recordings_mutex);
1211
                        if(rec == NULL || rec->destroyed) {
1212
                                JANUS_LOG(LOG_ERR, "No such recording\n");
1213
                                error_code = JANUS_RECORDPLAY_ERROR_NOT_FOUND;
1214
                                g_snprintf(error_cause, 512, "No such recording");
1215
                                goto error;
1216
                        }
1217
                        /* Access the frames */
1218
                        const char *warning = NULL;
1219
                        if(rec->arc_file) {
1220
                                session->aframes = janus_recordplay_get_frames(recordings_path, rec->arc_file);
1221
                                if(session->aframes == NULL) {
1222
                                        JANUS_LOG(LOG_WARN, "Error opening audio recording, trying to go on anyway\n");
1223
                                        warning = "Broken audio file, playing video only";
1224
                                }
1225
                        }
1226
                        if(rec->vrc_file) {
1227
                                session->vframes = janus_recordplay_get_frames(recordings_path, rec->vrc_file);
1228
                                if(session->vframes == NULL) {
1229
                                        JANUS_LOG(LOG_WARN, "Error opening video recording, trying to go on anyway\n");
1230
                                        warning = "Broken video file, playing audio only";
1231
                                }
1232
                        }
1233
                        if(session->aframes == NULL && session->vframes == NULL) {
1234
                                error_code = JANUS_RECORDPLAY_ERROR_INVALID_RECORDING;
1235
                                g_snprintf(error_cause, 512, "Error opening recording files");
1236
                                goto error;
1237
                        }
1238
                        session->recording = rec;
1239
                        session->recorder = FALSE;
1240
                        rec->viewers = g_list_append(rec->viewers, session);
1241
                        /* We need to prepare an offer */
1242
                        char sdptemp[1024], audio_mline[256], video_mline[512];
1243
                        if(session->aframes) {
1244
                                g_snprintf(audio_mline, 256, sdp_a_template,
1245
                                        OPUS_PT,                                                /* Opus payload type */
1246
                                        "sendonly",                                                /* Playout is sendonly */
1247
                                        OPUS_PT);                                                 /* Opus payload type */
1248
                        } else {
1249
                                audio_mline[0] = '\0';
1250
                        }
1251
                        if(session->vframes) {
1252
                                g_snprintf(video_mline, 512, sdp_v_template,
1253
                                        VP8_PT,                                                        /* VP8 payload type */
1254
                                        "sendonly",                                                /* Playout is sendonly */
1255
                                        VP8_PT,                                                 /* VP8 payload type */
1256
                                        VP8_PT,                                                 /* VP8 payload type */
1257
                                        VP8_PT,                                                 /* VP8 payload type */
1258
                                        VP8_PT,                                                 /* VP8 payload type */
1259
                                        VP8_PT);                                                 /* VP8 payload type */
1260
                        } else {
1261
                                video_mline[0] = '\0';
1262
                        }
1263
                        g_snprintf(sdptemp, 1024, sdp_template,
1264
                                janus_get_real_time(),                        /* We need current time here */
1265
                                janus_get_real_time(),                        /* We need current time here */
1266
                                session->recording->name,                /* Playout session */
1267
                                audio_mline,                                        /* Audio m-line, if any */
1268
                                video_mline);                                        /* Video m-line, if any */
1269
                        sdp = g_strdup(sdptemp);
1270
                        JANUS_LOG(LOG_VERB, "Going to offer this SDP:\n%s\n", sdp);
1271
                        /* Done! */
1272
                        result = json_object();
1273
                        json_object_set_new(result, "status", json_string("preparing"));
1274
                        json_object_set_new(result, "id", json_integer(id_value));
1275
                        if(warning)
1276
                                json_object_set_new(result, "warning", json_string(warning));
1277
                        /* Also notify event handlers */
1278
                        if(notify_events && gateway->events_is_enabled()) {
1279
                                json_t *info = json_object();
1280
                                json_object_set_new(info, "event", json_string("playout"));
1281
                                json_object_set_new(info, "id", json_integer(id_value));
1282
                                json_object_set_new(info, "audio", session->aframes ? json_true() : json_false());
1283
                                json_object_set_new(info, "video", session->vframes ? json_true() : json_false());
1284
                                gateway->notify_event(&janus_recordplay_plugin, session->handle, info);
1285
                        }
1286
                } else if(!strcasecmp(request_text, "start")) {
1287
                        if(!session->aframes && !session->vframes) {
1288
                                JANUS_LOG(LOG_ERR, "Not a playout session, can't start\n");
1289
                                error_code = JANUS_RECORDPLAY_ERROR_INVALID_STATE;
1290
                                g_snprintf(error_cause, 512, "Not a playout session, can't start");
1291
                                goto error;
1292
                        }
1293
                        /* Just a final message we make use of, e.g., to receive an ANSWER to our OFFER for a playout */
1294
                        if(!msg_sdp) {
1295
                                JANUS_LOG(LOG_ERR, "Missing SDP answer\n");
1296
                                error_code = JANUS_RECORDPLAY_ERROR_MISSING_ELEMENT;
1297
                                g_snprintf(error_cause, 512, "Missing SDP answer");
1298
                                goto error;
1299
                        }
1300
                        /* Done! */
1301
                        result = json_object();
1302
                        json_object_set_new(result, "status", json_string("playing"));
1303
                        /* Also notify event handlers */
1304
                        if(notify_events && gateway->events_is_enabled()) {
1305
                                json_t *info = json_object();
1306
                                json_object_set_new(info, "event", json_string("playing"));
1307
                                json_object_set_new(info, "id", json_integer(session->recording->id));
1308
                                gateway->notify_event(&janus_recordplay_plugin, session->handle, info);
1309
                        }
1310
                } else if(!strcasecmp(request_text, "stop")) {
1311
                        /* Stop the recording/playout */
1312
                        session->active = FALSE;
1313
                        janus_mutex_lock(&session->rec_mutex);
1314
                        if(session->arc) {
1315
                                janus_recorder_close(session->arc);
1316
                                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
1317
                                janus_recorder_free(session->arc);
1318
                        }
1319
                        session->arc = NULL;
1320
                        if(session->vrc) {
1321
                                janus_recorder_close(session->vrc);
1322
                                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
1323
                                janus_recorder_free(session->vrc);
1324
                        }
1325
                        session->vrc = NULL;
1326
                        janus_mutex_unlock(&session->rec_mutex);
1327
                        if(session->recorder) {
1328
                                /* Create a .nfo file for this recording */
1329
                                char nfofile[1024], nfo[1024];
1330
                                g_snprintf(nfofile, 1024, "%s/%"SCNu64".nfo", recordings_path, session->recording->id);
1331
                                FILE *file = fopen(nfofile, "wt");
1332
                                if(file == NULL) {
1333
                                        JANUS_LOG(LOG_ERR, "Error creating file %s...\n", nfofile);
1334
                                } else {
1335
                                        if(session->recording->arc_file && session->recording->vrc_file) {
1336
                                                g_snprintf(nfo, 1024,
1337
                                                        "[%"SCNu64"]\r\n"
1338
                                                        "name = %s\r\n"
1339
                                                        "date = %s\r\n"
1340
                                                        "audio = %s.mjr\r\n"
1341
                                                        "video = %s.mjr\r\n",
1342
                                                                session->recording->id, session->recording->name, session->recording->date,
1343
                                                                session->recording->arc_file, session->recording->vrc_file);
1344
                                        } else if(session->recording->arc_file) {
1345
                                                g_snprintf(nfo, 1024,
1346
                                                        "[%"SCNu64"]\r\n"
1347
                                                        "name = %s\r\n"
1348
                                                        "date = %s\r\n"
1349
                                                        "audio = %s.mjr\r\n",
1350
                                                                session->recording->id, session->recording->name, session->recording->date,
1351
                                                                session->recording->arc_file);
1352
                                        } else if(session->recording->vrc_file) {
1353
                                                g_snprintf(nfo, 1024,
1354
                                                        "[%"SCNu64"]\r\n"
1355
                                                        "name = %s\r\n"
1356
                                                        "date = %s\r\n"
1357
                                                        "video = %s.mjr\r\n",
1358
                                                                session->recording->id, session->recording->name, session->recording->date,
1359
                                                                session->recording->vrc_file);
1360
                                        }
1361
                                        /* Write to the file now */
1362
                                        fwrite(nfo, strlen(nfo), sizeof(char), file);
1363
                                        fclose(file);
1364
                                        session->recording->completed = TRUE;
1365
                                }
1366
                        }
1367
                        /* Done! */
1368
                        result = json_object();
1369
                        json_object_set_new(result, "status", json_string("stopped"));
1370
                        if(session->recording)
1371
                                json_object_set_new(result, "id", json_integer(session->recording->id));
1372
                        /* Also notify event handlers */
1373
                        if(notify_events && gateway->events_is_enabled()) {
1374
                                json_t *info = json_object();
1375
                                json_object_set_new(info, "event", json_string("stopped"));
1376
                                if(session->recording)
1377
                                        json_object_set_new(info, "id", json_integer(session->recording->id));
1378
                                gateway->notify_event(&janus_recordplay_plugin, session->handle, info);
1379
                        }
1380
                } else {
1381
                        JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
1382
                        error_code = JANUS_RECORDPLAY_ERROR_INVALID_REQUEST;
1383
                        g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1384
                        goto error;
1385
                }
1386

    
1387
                /* Any SDP to handle? */
1388
                if(msg_sdp) {
1389
                        session->firefox = strstr(msg_sdp, "Mozilla") ? TRUE : FALSE;
1390
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
1391
                }
1392

    
1393
                /* Prepare JSON event */
1394
                event = json_object();
1395
                json_object_set_new(event, "recordplay", json_string("event"));
1396
                if(result != NULL)
1397
                        json_object_set_new(event, "result", result);
1398
                if(!sdp) {
1399
                        int ret = gateway->push_event(msg->handle, &janus_recordplay_plugin, msg->transaction, event, NULL);
1400
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
1401
                        json_decref(event);
1402
                } else {
1403
                        const char *type = session->recorder ? "answer" : "offer";
1404
                        json_t *jsep = json_pack("{ssss}", "type", type, "sdp", sdp);
1405
                        /* How long will the gateway take to push the event? */
1406
                        g_atomic_int_set(&session->hangingup, 0);
1407
                        gint64 start = janus_get_monotonic_time();
1408
                        int res = gateway->push_event(msg->handle, &janus_recordplay_plugin, msg->transaction, event, jsep);
1409
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
1410
                                res, janus_get_monotonic_time()-start);
1411
                        g_free(sdp);
1412
                        json_decref(event);
1413
                        json_decref(jsep);
1414
                }
1415
                janus_recordplay_message_free(msg);
1416
                continue;
1417
                
1418
error:
1419
                {
1420
                        /* Prepare JSON error event */
1421
                        json_t *event = json_object();
1422
                        json_object_set_new(event, "recordplay", json_string("event"));
1423
                        json_object_set_new(event, "error_code", json_integer(error_code));
1424
                        json_object_set_new(event, "error", json_string(error_cause));
1425
                        int ret = gateway->push_event(msg->handle, &janus_recordplay_plugin, msg->transaction, event, NULL);
1426
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
1427
                        json_decref(event);
1428
                        janus_recordplay_message_free(msg);
1429
                }
1430
        }
1431
        JANUS_LOG(LOG_VERB, "LeavingRecord&Play handler thread\n");
1432
        return NULL;
1433
}
1434

    
1435
void janus_recordplay_update_recordings_list(void) {
1436
        if(recordings_path == NULL)
1437
                return;
1438
        JANUS_LOG(LOG_VERB, "Updating recordings list in %s\n", recordings_path);
1439
        janus_mutex_lock(&recordings_mutex);
1440
        /* First of all, let's keep track of which recordings are currently available */
1441
        GList *old_recordings = NULL;
1442
        if(recordings != NULL && g_hash_table_size(recordings) > 0) {
1443
                GHashTableIter iter;
1444
                gpointer value;
1445
                g_hash_table_iter_init(&iter, recordings);
1446
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1447
                        janus_recordplay_recording *rec = value;
1448
                        if(rec) {
1449
                                old_recordings = g_list_append(old_recordings, &rec->id);
1450
                        }
1451
                }
1452
                janus_mutex_unlock(&recordings_mutex);
1453
        }
1454
        /* Open dir */
1455
        DIR *dir = opendir(recordings_path);
1456
        if(!dir) {
1457
                JANUS_LOG(LOG_ERR, "Couldn't open folder...\n");
1458
                g_list_free(old_recordings);
1459
                return;
1460
        }
1461
        struct dirent *recent = NULL;
1462
        char recpath[1024];
1463
        while((recent = readdir(dir))) {
1464
                int len = strlen(recent->d_name);
1465
                if(len < 4)
1466
                        continue;
1467
                if(strcasecmp(recent->d_name+len-4, ".nfo"))
1468
                        continue;
1469
                JANUS_LOG(LOG_VERB, "Importing recording '%s'...\n", recent->d_name);
1470
                memset(recpath, 0, 1024);
1471
                g_snprintf(recpath, 1024, "%s/%s", recordings_path, recent->d_name);
1472
                janus_config *nfo = janus_config_parse(recpath);
1473
                if(nfo == NULL) { 
1474
                        JANUS_LOG(LOG_ERR, "Invalid recording '%s'...\n", recent->d_name);
1475
                        continue;
1476
                }
1477
                GList *cl = janus_config_get_categories(nfo);
1478
                if(cl == NULL || cl->data == NULL) {
1479
                        JANUS_LOG(LOG_WARN, "No recording info in '%s', skipping...\n", recent->d_name);
1480
                        janus_config_destroy(nfo);
1481
                        continue;
1482
                }
1483
                janus_config_category *cat = (janus_config_category *)cl->data;
1484
                guint64 id = g_ascii_strtoull(cat->name, NULL, 0);
1485
                if(id == 0) {
1486
                        JANUS_LOG(LOG_WARN, "Invalid ID, skipping...\n");
1487
                        janus_config_destroy(nfo);
1488
                        continue;
1489
                }
1490
                janus_recordplay_recording *rec = g_hash_table_lookup(recordings, &id);
1491
                if(rec != NULL) {
1492
                        JANUS_LOG(LOG_VERB, "Skipping recording with ID %"SCNu64", it's already in the list...\n", id);
1493
                        janus_config_destroy(nfo);
1494
                        /* Mark that we updated this recording */
1495
                        old_recordings = g_list_remove(old_recordings, &rec->id);
1496
                        continue;
1497
                }
1498
                janus_config_item *name = janus_config_get_item(cat, "name");
1499
                janus_config_item *date = janus_config_get_item(cat, "date");
1500
                janus_config_item *audio = janus_config_get_item(cat, "audio");
1501
                janus_config_item *video = janus_config_get_item(cat, "video");
1502
                if(!name || !name->value || strlen(name->value) == 0 || !date || !date->value || strlen(date->value) == 0) {
1503
                        JANUS_LOG(LOG_WARN, "Invalid info for recording %"SCNu64", skipping...\n", id);
1504
                        janus_config_destroy(nfo);
1505
                        continue;
1506
                }
1507
                if((!audio || !audio->value) && (!video || !video->value)) {
1508
                        JANUS_LOG(LOG_WARN, "No audio and no video in recording %"SCNu64", skipping...\n", id);
1509
                        janus_config_destroy(nfo);
1510
                        continue;
1511
                }
1512
                rec = (janus_recordplay_recording *)g_malloc0(sizeof(janus_recordplay_recording));
1513
                rec->id = id;
1514
                rec->name = g_strdup(name->value);
1515
                rec->date = g_strdup(date->value);
1516
                if(audio && audio->value) {
1517
                        rec->arc_file = g_strdup(audio->value);
1518
                        if(strstr(rec->arc_file, ".mjr")) {
1519
                                char *ext = strstr(rec->arc_file, ".mjr");
1520
                                *ext = '\0';
1521
                        }
1522
                }
1523
                if(video && video->value) {
1524
                        rec->vrc_file = g_strdup(video->value);
1525
                        if(strstr(rec->vrc_file, ".mjr")) {
1526
                                char *ext = strstr(rec->vrc_file, ".mjr");
1527
                                *ext = '\0';
1528
                        }
1529
                }
1530
                rec->viewers = NULL;
1531
                rec->destroyed = 0;
1532
                rec->completed = TRUE;
1533
                janus_mutex_init(&rec->mutex);
1534
                
1535
                janus_config_destroy(nfo);
1536

    
1537
                /* Add to the list of recordings */
1538
                g_hash_table_insert(recordings, janus_uint64_dup(rec->id), rec);
1539
        }
1540
        closedir(dir);
1541
        /* Now let's check if any of the previously existing recordings was removed */
1542
        if(old_recordings != NULL) {
1543
                while(old_recordings != NULL) {
1544
                        guint64 id = *((guint64 *)old_recordings->data);
1545
                        JANUS_LOG(LOG_VERB, "Recording %"SCNu64" is not available anymore, removing...\n", id);
1546
                        janus_recordplay_recording *old_rec = g_hash_table_lookup(recordings, &id);
1547
                        if(old_rec != NULL) {
1548
                                /* Remove it */
1549
                                g_hash_table_remove(recordings, &id);
1550
                                /* Only destroy the object if no one's watching, though */
1551
                                janus_mutex_lock(&old_rec->mutex);
1552
                                old_rec->destroyed = janus_get_monotonic_time();
1553
                                if(old_rec->viewers == NULL) {
1554
                                        JANUS_LOG(LOG_VERB, "Recording %"SCNu64" has no viewers, destroying it now\n", id);
1555
                                        janus_mutex_unlock(&old_rec->mutex);
1556
                                        g_free(old_rec->name);
1557
                                        g_free(old_rec->date);
1558
                                        g_free(old_rec->arc_file);
1559
                                        g_free(old_rec->vrc_file);
1560
                                        g_free(old_rec);
1561
                                } else {
1562
                                        JANUS_LOG(LOG_VERB, "Recording %"SCNu64" still has viewers, delaying its destruction until later\n", id);
1563
                                        janus_mutex_unlock(&old_rec->mutex);
1564
                                }
1565
                        }
1566
                        old_recordings = old_recordings->next;
1567
                }
1568
                g_list_free(old_recordings);
1569
        }
1570
        janus_mutex_unlock(&recordings_mutex);
1571
}
1572

    
1573
janus_recordplay_frame_packet *janus_recordplay_get_frames(const char *dir, const char *filename) {
1574
        if(!dir || !filename)
1575
                return NULL;
1576
        /* Open the file */
1577
        char source[1024];
1578
        if(strstr(filename, ".mjr"))
1579
                g_snprintf(source, 1024, "%s/%s", dir, filename);
1580
        else
1581
                g_snprintf(source, 1024, "%s/%s.mjr", dir, filename);
1582
        FILE *file = fopen(source, "rb");
1583
        if(file == NULL) {
1584
                JANUS_LOG(LOG_ERR, "Could not open file %s\n", source);
1585
                return NULL;
1586
        }
1587
        fseek(file, 0L, SEEK_END);
1588
        long fsize = ftell(file);
1589
        fseek(file, 0L, SEEK_SET);
1590
        JANUS_LOG(LOG_VERB, "File is %zu bytes\n", fsize);
1591

    
1592
        /* Pre-parse */
1593
        JANUS_LOG(LOG_VERB, "Pre-parsing file %s to generate ordered index...\n", source);
1594
        gboolean parsed_header = FALSE;
1595
        int bytes = 0;
1596
        long offset = 0;
1597
        uint16_t len = 0, count = 0;
1598
        uint32_t first_ts = 0, last_ts = 0, reset = 0;        /* To handle whether there's a timestamp reset in the recording */
1599
        char prebuffer[1500];
1600
        memset(prebuffer, 0, 1500);
1601
        /* Let's look for timestamp resets first */
1602
        while(offset < fsize) {
1603
                /* Read frame header */
1604
                fseek(file, offset, SEEK_SET);
1605
                bytes = fread(prebuffer, sizeof(char), 8, file);
1606
                if(bytes != 8 || prebuffer[0] != 'M') {
1607
                        JANUS_LOG(LOG_ERR, "Invalid header...\n");
1608
                        fclose(file);
1609
                        return NULL;
1610
                }
1611
                if(prebuffer[1] == 'E') {
1612
                        /* Either the old .mjr format header ('MEETECHO' header followed by 'audio' or 'video'), or a frame */
1613
                        offset += 8;
1614
                        bytes = fread(&len, sizeof(uint16_t), 1, file);
1615
                        len = ntohs(len);
1616
                        offset += 2;
1617
                        if(len == 5 && !parsed_header) {
1618
                                /* This is the main header */
1619
                                parsed_header = TRUE;
1620
                                JANUS_LOG(LOG_VERB, "Old .mjr header format\n");
1621
                                bytes = fread(prebuffer, sizeof(char), 5, file);
1622
                                if(prebuffer[0] == 'v') {
1623
                                        JANUS_LOG(LOG_INFO, "This is a video recording, assuming VP8\n");
1624
                                } else if(prebuffer[0] == 'a') {
1625
                                        JANUS_LOG(LOG_INFO, "This is an audio recording, assuming Opus\n");
1626
                                } else {
1627
                                        JANUS_LOG(LOG_WARN, "Unsupported recording media type...\n");
1628
                                        fclose(file);
1629
                                        return NULL;
1630
                                }
1631
                                offset += len;
1632
                                continue;
1633
                        } else if(len < 12) {
1634
                                /* Not RTP, skip */
1635
                                JANUS_LOG(LOG_VERB, "Skipping packet (not RTP?)\n");
1636
                                offset += len;
1637
                                continue;
1638
                        }
1639
                } else if(prebuffer[1] == 'J') {
1640
                        /* New .mjr format, the header may contain useful info */
1641
                        offset += 8;
1642
                        bytes = fread(&len, sizeof(uint16_t), 1, file);
1643
                        len = ntohs(len);
1644
                        offset += 2;
1645
                        if(len > 0 && !parsed_header) {
1646
                                /* This is the info header */
1647
                                JANUS_LOG(LOG_VERB, "New .mjr header format\n");
1648
                                bytes = fread(prebuffer, sizeof(char), len, file);
1649
                                parsed_header = TRUE;
1650
                                prebuffer[len] = '\0';
1651
                                json_error_t error;
1652
                                json_t *info = json_loads(prebuffer, 0, &error);
1653
                                if(!info) {
1654
                                        JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
1655
                                        JANUS_LOG(LOG_WARN, "Error parsing info header...\n");
1656
                                        fclose(file);
1657
                                        return NULL;
1658
                                }
1659
                                /* Is it audio or video? */
1660
                                json_t *type = json_object_get(info, "t");
1661
                                if(!type || !json_is_string(type)) {
1662
                                        JANUS_LOG(LOG_WARN, "Missing/invalid recording type in info header...\n");
1663
                                        json_decref(info);
1664
                                        fclose(file);
1665
                                        return NULL;
1666
                                }
1667
                                const char *t = json_string_value(type);
1668
                                int video = 0;
1669
                                gint64 c_time = 0, w_time = 0;
1670
                                if(!strcasecmp(t, "v")) {
1671
                                        video = 1;
1672
                                } else if(!strcasecmp(t, "a")) {
1673
                                        video = 0;
1674
                                } else {
1675
                                        JANUS_LOG(LOG_WARN, "Unsupported recording type '%s' in info header...\n", t);
1676
                                        json_decref(info);
1677
                                        fclose(file);
1678
                                        return NULL;
1679
                                }
1680
                                /* What codec was used? */
1681
                                json_t *codec = json_object_get(info, "c");
1682
                                if(!codec || !json_is_string(codec)) {
1683
                                        JANUS_LOG(LOG_WARN, "Missing recording codec in info header...\n");
1684
                                        json_decref(info);
1685
                                        fclose(file);
1686
                                        return NULL;
1687
                                }
1688
                                const char *c = json_string_value(codec);
1689
                                if(video && strcasecmp(c, "vp8")) {
1690
                                        JANUS_LOG(LOG_WARN, "The Record&Play plugin only supports VP8 video for now (was '%s')...\n", c);
1691
                                        json_decref(info);
1692
                                        fclose(file);
1693
                                        return NULL;
1694
                                } else if(!video && strcasecmp(c, "opus")) {
1695
                                        JANUS_LOG(LOG_WARN, "The Record&Play plugin only supports Opus audio for now (was '%s')...\n", c);
1696
                                        json_decref(info);
1697
                                        fclose(file);
1698
                                        return NULL;
1699
                                }
1700
                                /* When was the file created? */
1701
                                json_t *created = json_object_get(info, "s");
1702
                                if(!created || !json_is_integer(created)) {
1703
                                        JANUS_LOG(LOG_WARN, "Missing recording created time in info header...\n");
1704
                                        json_decref(info);
1705
                                        fclose(file);
1706
                                        return NULL;
1707
                                }
1708
                                c_time = json_integer_value(created);
1709
                                /* When was the first frame written? */
1710
                                json_t *written = json_object_get(info, "u");
1711
                                if(!written || !json_is_integer(written)) {
1712
                                        JANUS_LOG(LOG_WARN, "Missing recording written time in info header...\n");
1713
                                        json_decref(info);
1714
                                        fclose(file);
1715
                                        return NULL;
1716
                                }
1717
                                w_time = json_integer_value(created);
1718
                                /* Summary */
1719
                                JANUS_LOG(LOG_VERB, "This is %s recording:\n", video ? "a video" : "an audio");
1720
                                JANUS_LOG(LOG_VERB, "  -- Codec:   %s\n", c);
1721
                                JANUS_LOG(LOG_VERB, "  -- Created: %"SCNi64"\n", c_time);
1722
                                JANUS_LOG(LOG_VERB, "  -- Written: %"SCNi64"\n", w_time);
1723
                                json_decref(info);
1724
                        }
1725
                } else {
1726
                        JANUS_LOG(LOG_ERR, "Invalid header...\n");
1727
                        fclose(file);
1728
                        return NULL;
1729
                }
1730
                /* Only read RTP header */
1731
                bytes = fread(prebuffer, sizeof(char), 16, file);
1732
                rtp_header *rtp = (rtp_header *)prebuffer;
1733
                if(last_ts == 0) {
1734
                        first_ts = ntohl(rtp->timestamp);
1735
                        if(first_ts > 1000*1000)        /* Just used to check whether a packet is pre- or post-reset */
1736
                                first_ts -= 1000*1000;
1737
                } else {
1738
                        if(ntohl(rtp->timestamp) < last_ts) {
1739
                                /* The new timestamp is smaller than the next one, is it a timestamp reset or simply out of order? */
1740
                                if(last_ts-ntohl(rtp->timestamp) > 2*1000*1000*1000) {
1741
                                        reset = ntohl(rtp->timestamp);
1742
                                        JANUS_LOG(LOG_VERB, "Timestamp reset: %"SCNu32"\n", reset);
1743
                                }
1744
                        } else if(ntohl(rtp->timestamp) < reset) {
1745
                                JANUS_LOG(LOG_VERB, "Updating timestamp reset: %"SCNu32" (was %"SCNu32")\n", ntohl(rtp->timestamp), reset);
1746
                                reset = ntohl(rtp->timestamp);
1747
                        }
1748
                }
1749
                last_ts = ntohl(rtp->timestamp);
1750
                /* Skip data for now */
1751
                offset += len;
1752
        }
1753
        /* Now let's parse the frames and order them */
1754
        offset = 0;
1755
        janus_recordplay_frame_packet *list = NULL, *last = NULL;
1756
        while(offset < fsize) {
1757
                /* Read frame header */
1758
                fseek(file, offset, SEEK_SET);
1759
                bytes = fread(prebuffer, sizeof(char), 8, file);
1760
                prebuffer[8] = '\0';
1761
                JANUS_LOG(LOG_HUGE, "Header: %s\n", prebuffer);
1762
                offset += 8;
1763
                bytes = fread(&len, sizeof(uint16_t), 1, file);
1764
                len = ntohs(len);
1765
                JANUS_LOG(LOG_HUGE, "  -- Length: %"SCNu16"\n", len);
1766
                offset += 2;
1767
                if(prebuffer[1] == 'J' || len < 12) {
1768
                        /* Not RTP, skip */
1769
                        JANUS_LOG(LOG_HUGE, "  -- Not RTP, skipping\n");
1770
                        offset += len;
1771
                        continue;
1772
                }
1773
                /* Only read RTP header */
1774
                bytes = fread(prebuffer, sizeof(char), 16, file);
1775
                rtp_header *rtp = (rtp_header *)prebuffer;
1776
                JANUS_LOG(LOG_HUGE, "  -- RTP packet (ssrc=%"SCNu32", pt=%"SCNu16", ext=%"SCNu16", seq=%"SCNu16", ts=%"SCNu32")\n",
1777
                                ntohl(rtp->ssrc), rtp->type, rtp->extension, ntohs(rtp->seq_number), ntohl(rtp->timestamp));
1778
                /* Generate frame packet and insert in the ordered list */
1779
                janus_recordplay_frame_packet *p = g_malloc0(sizeof(janus_recordplay_frame_packet));
1780
                p->seq = ntohs(rtp->seq_number);
1781
                if(reset == 0) {
1782
                        /* Simple enough... */
1783
                        p->ts = ntohl(rtp->timestamp);
1784
                } else {
1785
                        /* Is this packet pre- or post-reset? */
1786
                        if(ntohl(rtp->timestamp) > first_ts) {
1787
                                /* Pre-reset... */
1788
                                p->ts = ntohl(rtp->timestamp);
1789
                        } else {
1790
                                /* Post-reset... */
1791
                                uint64_t max32 = UINT32_MAX;
1792
                                max32++;
1793
                                p->ts = max32+ntohl(rtp->timestamp);
1794
                        }
1795
                }
1796
                p->len = len;
1797
                p->offset = offset;
1798
                p->next = NULL;
1799
                p->prev = NULL;
1800
                if(list == NULL) {
1801
                        /* First element becomes the list itself (and the last item), at least for now */
1802
                        list = p;
1803
                        last = p;
1804
                } else {
1805
                        /* Check where we should insert this, starting from the end */
1806
                        int added = 0;
1807
                        janus_recordplay_frame_packet *tmp = last;
1808
                        while(tmp) {
1809
                                if(tmp->ts < p->ts) {
1810
                                        /* The new timestamp is greater than the last one we have, append */
1811
                                        added = 1;
1812
                                        if(tmp->next != NULL) {
1813
                                                /* We're inserting */
1814
                                                tmp->next->prev = p;
1815
                                                p->next = tmp->next;
1816
                                        } else {
1817
                                                /* Update the last packet */
1818
                                                last = p;
1819
                                        }
1820
                                        tmp->next = p;
1821
                                        p->prev = tmp;
1822
                                        break;
1823
                                } else if(tmp->ts == p->ts) {
1824
                                        /* Same timestamp, check the sequence number */
1825
                                        if(tmp->seq < p->seq && (abs(tmp->seq - p->seq) < 10000)) {
1826
                                                /* The new sequence number is greater than the last one we have, append */
1827
                                                added = 1;
1828
                                                if(tmp->next != NULL) {
1829
                                                        /* We're inserting */
1830
                                                        tmp->next->prev = p;
1831
                                                        p->next = tmp->next;
1832
                                                } else {
1833
                                                        /* Update the last packet */
1834
                                                        last = p;
1835
                                                }
1836
                                                tmp->next = p;
1837
                                                p->prev = tmp;
1838
                                                break;
1839
                                        } else if(tmp->seq > p->seq && (abs(tmp->seq - p->seq) > 10000)) {
1840
                                                /* The new sequence number (resetted) is greater than the last one we have, append */
1841
                                                added = 1;
1842
                                                if(tmp->next != NULL) {
1843
                                                        /* We're inserting */
1844
                                                        tmp->next->prev = p;
1845
                                                        p->next = tmp->next;
1846
                                                } else {
1847
                                                        /* Update the last packet */
1848
                                                        last = p;
1849
                                                }
1850
                                                tmp->next = p;
1851
                                                p->prev = tmp;
1852
                                                break;
1853
                                        }
1854
                                }
1855
                                /* If either the timestamp ot the sequence number we just got is smaller, keep going back */
1856
                                tmp = tmp->prev;
1857
                        }
1858
                        if(!added) {
1859
                                /* We reached the start */
1860
                                p->next = list;
1861
                                list->prev = p;
1862
                                list = p;
1863
                        }
1864
                }
1865
                /* Skip data for now */
1866
                offset += len;
1867
                count++;
1868
        }
1869
        
1870
        JANUS_LOG(LOG_VERB, "Counted %"SCNu16" RTP packets\n", count);
1871
        janus_recordplay_frame_packet *tmp = list;
1872
        count = 0;
1873
        while(tmp) {
1874
                count++;
1875
                JANUS_LOG(LOG_HUGE, "[%10lu][%4d] seq=%"SCNu16", ts=%"SCNu64"\n", tmp->offset, tmp->len, tmp->seq, tmp->ts);
1876
                tmp = tmp->next;
1877
        }
1878
        JANUS_LOG(LOG_VERB, "Counted %"SCNu16" frame packets\n", count);
1879
        
1880
        /* Done! */
1881
        fclose(file);
1882
        return list;
1883
}
1884

    
1885
static void *janus_recordplay_playout_thread(void *data) {
1886
        janus_recordplay_session *session = (janus_recordplay_session *)data;
1887
        if(!session) {
1888
                JANUS_LOG(LOG_ERR, "Invalid session, can't start playout thread...\n");
1889
                g_thread_unref(g_thread_self());
1890
                return NULL;
1891
        }
1892
        if(session->recorder) {
1893
                JANUS_LOG(LOG_ERR, "This is a recorder, can't start playout thread...\n");
1894
                g_thread_unref(g_thread_self());
1895
                return NULL;
1896
        }
1897
        if(!session->aframes && !session->vframes) {
1898
                JANUS_LOG(LOG_ERR, "No audio and no video frames, can't start playout thread...\n");
1899
                g_thread_unref(g_thread_self());
1900
                return NULL;
1901
        }
1902
        JANUS_LOG(LOG_INFO, "Joining playout thread\n");
1903
        /* Open the files */
1904
        FILE *afile = NULL, *vfile = NULL;
1905
        if(session->aframes) {
1906
                char source[1024];
1907
                if(strstr(session->recording->arc_file, ".mjr"))
1908
                        g_snprintf(source, 1024, "%s/%s", recordings_path, session->recording->arc_file);
1909
                else
1910
                        g_snprintf(source, 1024, "%s/%s.mjr", recordings_path, session->recording->arc_file);
1911
                afile = fopen(source, "rb");
1912
                if(afile == NULL) {
1913
                        JANUS_LOG(LOG_ERR, "Could not open audio file %s, can't start playout thread...\n", source);
1914
                        g_thread_unref(g_thread_self());
1915
                        return NULL;
1916
                }
1917
        }
1918
        if(session->vframes) {
1919
                char source[1024];
1920
                if(strstr(session->recording->vrc_file, ".mjr"))
1921
                        g_snprintf(source, 1024, "%s/%s", recordings_path, session->recording->vrc_file);
1922
                else
1923
                        g_snprintf(source, 1024, "%s/%s.mjr", recordings_path, session->recording->vrc_file);
1924
                vfile = fopen(source, "rb");
1925
                if(vfile == NULL) {
1926
                        JANUS_LOG(LOG_ERR, "Could not open video file %s, can't start playout thread...\n", source);
1927
                        if(afile)
1928
                                fclose(afile);
1929
                        afile = NULL;
1930
                        g_thread_unref(g_thread_self());
1931
                        return NULL;
1932
                }
1933
        }
1934
        
1935
        /* Timer */
1936
        gboolean asent = FALSE, vsent = FALSE;
1937
        struct timeval now, abefore, vbefore;
1938
        time_t d_s, d_us;
1939
        gettimeofday(&now, NULL);
1940
        gettimeofday(&abefore, NULL);
1941
        gettimeofday(&vbefore, NULL);
1942

    
1943
        janus_recordplay_frame_packet *audio = session->aframes, *video = session->vframes;
1944
        char *buffer = (char *)g_malloc0(1500);
1945
        memset(buffer, 0, 1500);
1946
        int bytes = 0;
1947
        int64_t ts_diff = 0, passed = 0;
1948
        
1949
        while(!session->destroyed && session->active && !session->recording->destroyed && (audio || video)) {
1950
                if(!asent && !vsent) {
1951
                        /* We skipped the last round, so sleep a bit (5ms) */
1952
                        usleep(5000);
1953
                }
1954
                asent = FALSE;
1955
                vsent = FALSE;
1956
                if(audio) {
1957
                        if(audio == session->aframes) {
1958
                                /* First packet, send now */
1959
                                fseek(afile, audio->offset, SEEK_SET);
1960
                                bytes = fread(buffer, sizeof(char), audio->len, afile);
1961
                                if(bytes != audio->len)
1962
                                        JANUS_LOG(LOG_WARN, "Didn't manage to read all the bytes we needed (%d < %d)...\n", bytes, audio->len);
1963
                                /* Update payload type */
1964
                                rtp_header *rtp = (rtp_header *)buffer;
1965
                                rtp->type = OPUS_PT;        /* FIXME We assume it's Opus */
1966
                                if(gateway != NULL)
1967
                                        gateway->relay_rtp(session->handle, 0, (char *)buffer, bytes);
1968
                                gettimeofday(&now, NULL);
1969
                                abefore.tv_sec = now.tv_sec;
1970
                                abefore.tv_usec = now.tv_usec;
1971
                                asent = TRUE;
1972
                                audio = audio->next;
1973
                        } else {
1974
                                /* What's the timestamp skip from the previous packet? */
1975
                                ts_diff = audio->ts - audio->prev->ts;
1976
                                ts_diff = (ts_diff*1000)/48;        /* FIXME Again, we're assuming Opus and it's 48khz */
1977
                                /* Check if it's time to send */
1978
                                gettimeofday(&now, NULL);
1979
                                d_s = now.tv_sec - abefore.tv_sec;
1980
                                d_us = now.tv_usec - abefore.tv_usec;
1981
                                if(d_us < 0) {
1982
                                        d_us += 1000000;
1983
                                        --d_s;
1984
                                }
1985
                                passed = d_s*1000000 + d_us;
1986
                                if(passed < (ts_diff-5000)) {
1987
                                        asent = FALSE;
1988
                                } else {
1989
                                        /* Update the reference time */
1990
                                        abefore.tv_usec += ts_diff%1000000;
1991
                                        if(abefore.tv_usec > 1000000) {
1992
                                                abefore.tv_sec++;
1993
                                                abefore.tv_usec -= 1000000;
1994
                                        }
1995
                                        if(ts_diff/1000000 > 0) {
1996
                                                abefore.tv_sec += ts_diff/1000000;
1997
                                                abefore.tv_usec -= ts_diff/1000000;
1998
                                        }
1999
                                        /* Send now */
2000
                                        fseek(afile, audio->offset, SEEK_SET);
2001
                                        bytes = fread(buffer, sizeof(char), audio->len, afile);
2002
                                        if(bytes != audio->len)
2003
                                                JANUS_LOG(LOG_WARN, "Didn't manage to read all the bytes we needed (%d < %d)...\n", bytes, audio->len);
2004
                                        /* Update payload type */
2005
                                        rtp_header *rtp = (rtp_header *)buffer;
2006
                                        rtp->type = OPUS_PT;        /* FIXME We assume it's Opus */
2007
                                        if(gateway != NULL)
2008
                                                gateway->relay_rtp(session->handle, 0, (char *)buffer, bytes);
2009
                                        asent = TRUE;
2010
                                        audio = audio->next;
2011
                                }
2012
                        }
2013
                }
2014
                if(video) {
2015
                        if(video == session->vframes) {
2016
                                /* First packets: there may be many of them with the same timestamp, send them all */
2017
                                uint64_t ts = video->ts;
2018
                                while(video && video->ts == ts) {
2019
                                        fseek(vfile, video->offset, SEEK_SET);
2020
                                        bytes = fread(buffer, sizeof(char), video->len, vfile);
2021
                                        if(bytes != video->len)
2022
                                                JANUS_LOG(LOG_WARN, "Didn't manage to read all the bytes we needed (%d < %d)...\n", bytes, video->len);
2023
                                        /* Update payload type */
2024
                                        rtp_header *rtp = (rtp_header *)buffer;
2025
                                        rtp->type = VP8_PT;        /* FIXME We assume it's VP8 */
2026
                                        if(gateway != NULL)
2027
                                                gateway->relay_rtp(session->handle, 1, (char *)buffer, bytes);
2028
                                        video = video->next;
2029
                                }
2030
                                vsent = TRUE;
2031
                                gettimeofday(&now, NULL);
2032
                                vbefore.tv_sec = now.tv_sec;
2033
                                vbefore.tv_usec = now.tv_usec;
2034
                        } else {
2035
                                /* What's the timestamp skip from the previous packet? */
2036
                                ts_diff = video->ts - video->prev->ts;
2037
                                ts_diff = (ts_diff*1000)/90;
2038
                                /* Check if it's time to send */
2039
                                gettimeofday(&now, NULL);
2040
                                d_s = now.tv_sec - vbefore.tv_sec;
2041
                                d_us = now.tv_usec - vbefore.tv_usec;
2042
                                if(d_us < 0) {
2043
                                        d_us += 1000000;
2044
                                        --d_s;
2045
                                }
2046
                                passed = d_s*1000000 + d_us;
2047
                                if(passed < (ts_diff-5000)) {
2048
                                        vsent = FALSE;
2049
                                } else {
2050
                                        /* Update the reference time */
2051
                                        vbefore.tv_usec += ts_diff%1000000;
2052
                                        if(vbefore.tv_usec > 1000000) {
2053
                                                vbefore.tv_sec++;
2054
                                                vbefore.tv_usec -= 1000000;
2055
                                        }
2056
                                        if(ts_diff/1000000 > 0) {
2057
                                                vbefore.tv_sec += ts_diff/1000000;
2058
                                                vbefore.tv_usec -= ts_diff/1000000;
2059
                                        }
2060
                                        /* There may be multiple packets with the same timestamp, send them all */
2061
                                        uint64_t ts = video->ts;
2062
                                        while(video && video->ts == ts) {
2063
                                                /* Send now */
2064
                                                fseek(vfile, video->offset, SEEK_SET);
2065
                                                bytes = fread(buffer, sizeof(char), video->len, vfile);
2066
                                                if(bytes != video->len)
2067
                                                        JANUS_LOG(LOG_WARN, "Didn't manage to read all the bytes we needed (%d < %d)...\n", bytes, video->len);
2068
                                                /* Update payload type */
2069
                                                rtp_header *rtp = (rtp_header *)buffer;
2070
                                                rtp->type = VP8_PT;        /* FIXME We assume it's VP8 */
2071
                                                if(gateway != NULL)
2072
                                                        gateway->relay_rtp(session->handle, 1, (char *)buffer, bytes);
2073
                                                video = video->next;
2074
                                        }
2075
                                        vsent = TRUE;
2076
                                }
2077
                        }
2078
                }
2079
        }
2080
        
2081
        g_free(buffer);
2082

    
2083
        /* Get rid of the indexes */
2084
        janus_recordplay_frame_packet *tmp = NULL;
2085
        audio = session->aframes;
2086
        while(audio) {
2087
                tmp = audio->next;
2088
                g_free(audio);
2089
                audio = tmp;
2090
        }
2091
        session->aframes = NULL;
2092
        video = session->vframes;
2093
        while(video) {
2094
                tmp = video->next;
2095
                g_free(video);
2096
                video = tmp;
2097
        }
2098
        session->vframes = NULL;
2099

    
2100
        if(afile)
2101
                fclose(afile);
2102
        afile = NULL;
2103
        if(vfile)
2104
                fclose(vfile);
2105
        vfile = NULL;
2106

    
2107
        if(session->recording->destroyed) {
2108
                /* Remove from the list of viewers */
2109
                janus_mutex_lock(&session->recording->mutex);
2110
                session->recording->viewers = g_list_remove(session->recording->viewers, session);
2111
                if(session->recording->viewers == NULL) {
2112
                        /* This was the last viewer, destroying the recording */
2113
                        JANUS_LOG(LOG_VERB, "Last viewer stopped playout of recording %"SCNu64", destroying it now\n", session->recording->id);
2114
                        janus_mutex_unlock(&session->recording->mutex);
2115
                        g_free(session->recording->name);
2116
                        g_free(session->recording->date);
2117
                        g_free(session->recording->arc_file);
2118
                        g_free(session->recording->vrc_file);
2119
                        g_free(session->recording);
2120
                        session->recording = NULL;
2121
                } else {
2122
                        /* Other viewers still on, don't do anything */
2123
                        JANUS_LOG(LOG_VERB, "Recording %"SCNu64" still has viewers, delaying its destruction until later\n", session->recording->id);
2124
                        janus_mutex_unlock(&session->recording->mutex);
2125
                }
2126
        }
2127

    
2128
        /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
2129
        gateway->close_pc(session->handle);
2130
        
2131
        JANUS_LOG(LOG_INFO, "Leaving playout thread\n");
2132
        g_thread_unref(g_thread_self());
2133
        return NULL;
2134
}