/plugins/janus_videoroom.c
https://github.com/michaelsharpe/janus-gateway · C · 1474 lines · 1272 code · 55 blank · 147 comment · 351 complexity · 0144ce9361b8e0bef8e99b0a114685bf MD5 · raw file
- /*! \file janus_videoroom.c
- * \author Lorenzo Miniero <lorenzo@meetecho.com>
- * \copyright GNU Affero General Public License v3
- * \brief Janus VideoRoom plugin
- * \details This is a plugin implementing a videoconferencing MCU for Janus.
- * This means that the plugin implements a virtual conferencing room peers
- * can join and leave at any time. This room is based on a Publish/Subscribe
- * pattern. Each peer can publish his/her own live audio/video feeds: this
- * feed becomes an available stream in the room the other participants can
- * attach to. This means that this plugin allows the realization of several
- * different scenarios, ranging from a simple webinar (one speaker, several
- * listeners) to a fully meshed video conference (each peer sending and
- * receiving to and from all the others).
- *
- * Considering that this plugin allows for several different WebRTC PeerConnections
- * to be on at the same time for the same peer (specifically, each peer
- * potentially has 1 PeerConnection on for publishing and N on for subscriptions
- * from other peers), each peer may need to attach several times to the same
- * plugin for every stream: this means that each peer needs to have at least one
- * handle active for managing its relation with the plugin (joining a room,
- * leaving a room, muting/unmuting, publishing, receiving events), and needs
- * to open a new one each time he/she wants to subscribe to a feed from
- * another participant. The handle used for a subscription, however, would
- * be logically a "slave" to the master one used for managing the room: this
- * means that it cannot be used, for instance, to unmute in the room, as its
- * only purpose would be to provide a context in which creating the sendonly
- * PeerConnection for the subscription to the active participant.
- *
- * Rooms to make available are listed in the plugin configuration file.
- * A pre-filled configuration file is provided in \c conf/janus.plugin.videoroom.cfg
- * and includes a demo room for testing. The same plugin is also used
- * dynamically (that is, with rooms created on the fly via API) in the
- * Screen Sharing demo as well.
- *
- * To add more rooms or modify the existing one, you can use the following
- * syntax:
- *
- * \verbatim
- [<unique room ID>]
- description = This is my awesome room
- publishers = <max number of concurrent senders> (e.g., 6 for a video
- conference or 1 for a webinar)
- bitrate = <max video bitrate for senders> (e.g., 128000)
- fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
- \endverbatim
- *
- * \ingroup plugins
- * \ref plugins
- */
- #include "plugin.h"
- #include <jansson.h>
- #include "../apierror.h"
- #include "../config.h"
- #include "../mutex.h"
- #include "../rtcp.h"
- #include "../utils.h"
- /* Plugin information */
- #define JANUS_VIDEOROOM_VERSION 2
- #define JANUS_VIDEOROOM_VERSION_STRING "0.0.2"
- #define JANUS_VIDEOROOM_DESCRIPTION "This is a plugin implementing a videoconferencing MCU for Janus, something like Licode."
- #define JANUS_VIDEOROOM_NAME "JANUS VideoRoom plugin"
- #define JANUS_VIDEOROOM_AUTHOR "Meetecho s.r.l."
- #define JANUS_VIDEOROOM_PACKAGE "janus.plugin.videoroom"
- /* Plugin methods */
- janus_plugin *create(void);
- int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
- void janus_videoroom_destroy(void);
- int janus_videoroom_get_version(void);
- const char *janus_videoroom_get_version_string(void);
- const char *janus_videoroom_get_description(void);
- const char *janus_videoroom_get_name(void);
- const char *janus_videoroom_get_author(void);
- const char *janus_videoroom_get_package(void);
- void janus_videoroom_create_session(janus_plugin_session *handle, int *error);
- void janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp);
- void janus_videoroom_setup_media(janus_plugin_session *handle);
- void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
- void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
- void janus_videoroom_hangup_media(janus_plugin_session *handle);
- void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error);
- /* Plugin setup */
- static janus_plugin janus_videoroom_plugin =
- {
- .init = janus_videoroom_init,
- .destroy = janus_videoroom_destroy,
- .get_version = janus_videoroom_get_version,
- .get_version_string = janus_videoroom_get_version_string,
- .get_description = janus_videoroom_get_description,
- .get_name = janus_videoroom_get_name,
- .get_author = janus_videoroom_get_author,
- .get_package = janus_videoroom_get_package,
-
- .create_session = janus_videoroom_create_session,
- .handle_message = janus_videoroom_handle_message,
- .setup_media = janus_videoroom_setup_media,
- .incoming_rtp = janus_videoroom_incoming_rtp,
- .incoming_rtcp = janus_videoroom_incoming_rtcp,
- .hangup_media = janus_videoroom_hangup_media,
- .destroy_session = janus_videoroom_destroy_session,
- };
- /* Plugin creator */
- janus_plugin *create(void) {
- JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
- return &janus_videoroom_plugin;
- }
- /* Useful stuff */
- static int initialized = 0, stopping = 0;
- static janus_callbacks *gateway = NULL;
- static GThread *handler_thread;
- static void *janus_videoroom_handler(void *data);
- static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
- char *string_replace(char *message, char *old, char *new, int *modified);
- typedef enum janus_videoroom_p_type {
- janus_videoroom_p_type_none = 0,
- janus_videoroom_p_type_subscriber,
- janus_videoroom_p_type_publisher,
- } janus_videoroom_p_type;
- typedef struct janus_videoroom_message {
- janus_plugin_session *handle;
- char *transaction;
- char *message;
- char *sdp_type;
- char *sdp;
- } janus_videoroom_message;
- GQueue *messages;
- void janus_videoroom_message_free(janus_videoroom_message *msg);
- void janus_videoroom_message_free(janus_videoroom_message *msg) {
- if(!msg)
- return;
- msg->handle = NULL;
- if(msg->transaction != NULL)
- g_free(msg->transaction);
- msg->transaction = NULL;
- if(msg->message != NULL)
- g_free(msg->message);
- msg->message = NULL;
- if(msg->sdp_type != NULL)
- g_free(msg->sdp_type);
- msg->sdp_type = NULL;
- if(msg->sdp != NULL)
- g_free(msg->sdp);
- msg->sdp = NULL;
- g_free(msg);
- msg = NULL;
- }
- typedef struct janus_videoroom {
- guint64 room_id; /* Unique room ID */
- gchar *room_name; /* Room description */
- int max_publishers; /* Maximum number of concurrent publishers */
- uint64_t bitrate; /* Global bitrate limit */
- uint16_t fir_freq; /* Regular FIR frequency (0=disabled) */
- gboolean destroy;
- GHashTable *participants; /* Map of potential publishers (we get listeners from them) */
- } janus_videoroom;
- GHashTable *rooms;
- janus_mutex rooms_mutex;
- typedef struct janus_videoroom_session {
- janus_plugin_session *handle;
- janus_videoroom_p_type participant_type;
- gpointer participant;
- gboolean started;
- gboolean stopping;
- gboolean destroy;
- } janus_videoroom_session;
- GHashTable *sessions;
- janus_mutex sessions_mutex;
- typedef struct janus_videoroom_participant {
- janus_videoroom_session *session;
- janus_videoroom *room; /* Room */
- guint64 user_id; /* Unique ID in the room */
- gchar *display; /* Display name (just for fun) */
- gchar *sdp; /* The SDP this publisher negotiated, if any */
- gboolean audio_active;
- gboolean video_active;
- gboolean firefox; /* We send Firefox users a different kind of FIR */
- uint64_t bitrate;
- gint64 fir_latest; /* Time of latest sent FIR (to avoid flooding) */
- gint fir_seq; /* FIR sequence number */
- GSList *listeners;
- janus_mutex listeners_mutex;
- } janus_videoroom_participant;
- typedef struct janus_videoroom_listener {
- janus_videoroom_session *session;
- janus_videoroom *room; /* Room */
- janus_videoroom_participant *feed; /* Participant this listener is subscribed to */
- gboolean paused;
- } janus_videoroom_listener;
- typedef struct janus_videoroom_rtp_relay_packet {
- char *data;
- gint length;
- gint is_video;
- } janus_videoroom_rtp_relay_packet;
- /* SDP offer/answer templates */
- static const char *sdp_template =
- "v=0\r\n"
- "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n" /* We need current time here */
- "s=%s\r\n" /* Video room name */
- "t=0 0\r\n"
- "%s%s"; /* Audio and/or video m-lines */
- static const char *sdp_a_template =
- "m=audio 1 RTP/SAVPF %d\r\n" /* Opus payload type */
- "c=IN IP4 1.1.1.1\r\n"
- "a=%s\r\n" /* Media direction */
- "a=rtpmap:%d opus/48000/2\r\n"; /* Opus payload type */
- static const char *sdp_v_template =
- "m=video 1 RTP/SAVPF %d\r\n" /* VP8 payload type */
- "c=IN IP4 1.1.1.1\r\n"
- "b=AS:%d\r\n" /* Bandwidth */
- "a=%s\r\n" /* Media direction */
- "a=rtpmap:%d VP8/90000\r\n" /* VP8 payload type */
- "a=rtcp-fb:%d ccm fir\r\n" /* VP8 payload type */
- "a=rtcp-fb:%d nack\r\n" /* VP8 payload type */
- "a=rtcp-fb:%d nack pli\r\n" /* VP8 payload type */
- "a=rtcp-fb:%d goog-remb\r\n"; /* VP8 payload type */
- /* Error codes */
- #define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR 499
- #define JANUS_VIDEOROOM_ERROR_NO_MESSAGE 421
- #define JANUS_VIDEOROOM_ERROR_INVALID_JSON 422
- #define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST 423
- #define JANUS_VIDEOROOM_ERROR_JOIN_FIRST 424
- #define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED 425
- #define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM 426
- #define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED 427
- #define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT 428
- #define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT 429
- #define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE 430
- #define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL 431
- /* Plugin implementation */
- int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
- if(stopping) {
- /* Still stopping from before */
- return -1;
- }
- if(callback == NULL || config_path == NULL) {
- /* Invalid arguments */
- return -1;
- }
- /* Read configuration */
- char filename[255];
- sprintf(filename, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
- JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
- janus_config *config = janus_config_parse(filename);
- if(config != NULL)
- janus_config_print(config);
- rooms = g_hash_table_new(NULL, NULL);
- janus_mutex_init(&rooms_mutex);
- sessions = g_hash_table_new(NULL, NULL);
- janus_mutex_init(&sessions_mutex);
- messages = g_queue_new();
- /* This is the callback we'll need to invoke to contact the gateway */
- gateway = callback;
- /* Parse configuration to populate the rooms list */
- if(config != NULL) {
- janus_config_category *cat = janus_config_get_categories(config);
- while(cat != NULL) {
- if(cat->name == NULL) {
- cat = cat->next;
- continue;
- }
- JANUS_LOG(LOG_VERB, "Adding video room '%s'\n", cat->name);
- janus_config_item *desc = janus_config_get_item(cat, "description");
- janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
- janus_config_item *maxp = janus_config_get_item(cat, "publishers");
- janus_config_item *firfreq = janus_config_get_item(cat, "fir_freq");
- /* Create the video mcu room */
- janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
- if(videoroom == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- continue;
- }
- videoroom->room_id = atoi(cat->name);
- char *description = NULL;
- if(desc != NULL && desc->value != NULL)
- description = g_strdup(desc->value);
- else
- description = g_strdup(cat->name);
- if(description == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- continue;
- }
- videoroom->room_name = description;
- videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
- if(maxp != NULL && maxp->value != NULL)
- videoroom->max_publishers = atol(maxp->value);
- if(videoroom->max_publishers < 0)
- videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
- videoroom->bitrate = 0;
- if(bitrate != NULL && bitrate->value != NULL)
- videoroom->bitrate = atol(bitrate->value);
- if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
- videoroom->bitrate = 64000; /* Don't go below 64k */
- videoroom->fir_freq = 0;
- if(firfreq != NULL && firfreq->value != NULL)
- videoroom->fir_freq = atol(firfreq->value);
- videoroom->destroy = 0;
- videoroom->participants = g_hash_table_new(NULL, NULL);
- janus_mutex_lock(&rooms_mutex);
- g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
- janus_mutex_unlock(&rooms_mutex);
- JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
- cat = cat->next;
- }
- /* Done */
- janus_config_destroy(config);
- config = NULL;
- }
- /* Show available rooms */
- janus_mutex_lock(&rooms_mutex);
- GList *rooms_list = g_hash_table_get_values(rooms);
- GList *r = rooms_list;
- while(r) {
- janus_videoroom *vr = (janus_videoroom *)r->data;
- JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
- r = r->next;
- }
- g_list_free(rooms_list);
- janus_mutex_unlock(&rooms_mutex);
- initialized = 1;
- /* Launch the thread that will handle incoming messages */
- GError *error = NULL;
- handler_thread = g_thread_try_new("janus videoroom handler", janus_videoroom_handler, NULL, &error);
- if(error != NULL) {
- initialized = 0;
- /* Something went wrong... */
- JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??");
- return -1;
- }
- JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
- return 0;
- }
- void janus_videoroom_destroy() {
- if(!initialized)
- return;
- stopping = 1;
- if(handler_thread != NULL) {
- g_thread_join(handler_thread);
- }
- handler_thread = NULL;
- /* FIXME We should destroy the sessions cleanly */
- janus_mutex_lock(&sessions_mutex);
- g_hash_table_destroy(sessions);
- janus_mutex_unlock(&sessions_mutex);
- janus_mutex_lock(&rooms_mutex);
- g_hash_table_destroy(rooms);
- janus_mutex_unlock(&rooms_mutex);
- g_queue_free(messages);
- rooms = NULL;
- initialized = 0;
- stopping = 0;
- JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
- }
- int janus_videoroom_get_version() {
- return JANUS_VIDEOROOM_VERSION;
- }
- const char *janus_videoroom_get_version_string() {
- return JANUS_VIDEOROOM_VERSION_STRING;
- }
- const char *janus_videoroom_get_description() {
- return JANUS_VIDEOROOM_DESCRIPTION;
- }
- const char *janus_videoroom_get_name() {
- return JANUS_VIDEOROOM_NAME;
- }
- const char *janus_videoroom_get_author() {
- return JANUS_VIDEOROOM_AUTHOR;
- }
- const char *janus_videoroom_get_package() {
- return JANUS_VIDEOROOM_PACKAGE;
- }
- void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
- if(stopping || !initialized) {
- *error = -1;
- return;
- }
- janus_videoroom_session *session = (janus_videoroom_session *)calloc(1, sizeof(janus_videoroom_session));
- if(session == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- *error = -2;
- return;
- }
- session->handle = handle;
- session->participant_type = janus_videoroom_p_type_none;
- session->participant = NULL;
- handle->plugin_handle = session;
- janus_mutex_lock(&sessions_mutex);
- g_hash_table_insert(sessions, handle, session);
- janus_mutex_unlock(&sessions_mutex);
- return;
- }
- void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
- if(stopping || !initialized) {
- *error = -1;
- return;
- }
- janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
- if(!session) {
- JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
- *error = -2;
- return;
- }
- if(session->destroy) {
- JANUS_LOG(LOG_WARN, "Session already destroyed...\n");
- return;
- }
- JANUS_LOG(LOG_VERB, "Removing Video Room session...\n");
- janus_mutex_lock(&sessions_mutex);
- g_hash_table_remove(sessions, handle);
- janus_mutex_unlock(&sessions_mutex);
- if(session->participant_type == janus_videoroom_p_type_publisher) {
- /* TODO Get rid of this publisher and its listeners */
- } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
- /* TODO Detach this listener from its subscriber */
- }
- janus_videoroom_hangup_media(handle);
- /* Cleaning up and removing the session is done in a lazy way */
- session->destroy = TRUE;
- return;
- }
- void janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
- if(stopping || !initialized)
- return;
- JANUS_LOG(LOG_VERB, "%s\n", message);
- janus_videoroom_message *msg = calloc(1, sizeof(janus_videoroom_message));
- if(msg == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- return;
- }
- msg->handle = handle;
- msg->transaction = transaction;
- msg->message = message;
- msg->sdp_type = sdp_type;
- msg->sdp = sdp;
- g_queue_push_tail(messages, msg);
- }
- void janus_videoroom_setup_media(janus_plugin_session *handle) {
- JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
- if(stopping || !initialized)
- return;
- janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
- if(!session) {
- JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
- return;
- }
- if(session->destroy)
- return;
- /* Media relaying can start now */
- session->started = TRUE;
- /* If this is a listener, ask the publisher a FIR */
- if(session->participant && session->participant_type == janus_videoroom_p_type_subscriber) {
- janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
- if(l && l->feed) {
- janus_videoroom_participant *p = l->feed;
- if(p && p->session) {
- /* Send a FIR */
- char buf[20];
- memset(buf, 0, 20);
- if(!p->firefox)
- janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
- else
- janus_rtcp_fir_legacy((char *)&buf, 20, &p->fir_seq);
- JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %s\n", p->display);
- gateway->relay_rtcp(p->session->handle, 1, buf, 20);
- /* Send a PLI too, just in case... */
- memset(buf, 0, 12);
- janus_rtcp_pli((char *)&buf, 12);
- JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %s\n", p->display);
- gateway->relay_rtcp(p->session->handle, 1, buf, 12);
- }
- }
- }
- }
- void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
- if(handle == NULL || handle->stopped || stopping || !initialized || !gateway)
- return;
- janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
- if(!session || session->destroy || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
- return;
- janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
- if((!video && participant->audio_active) || (video && participant->video_active)) {
- janus_videoroom_rtp_relay_packet packet;
- packet.data = buf;
- packet.length = len;
- packet.is_video = video;
- g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
- if(video && participant->video_active && (participant->room->fir_freq > 0)) {
- /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
- gint64 now = janus_get_monotonic_time();
- if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
- /* FIXME We send a FIR every tot seconds */
- participant->fir_latest = now;
- char buf[20];
- memset(buf, 0, 20);
- janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
- //~ if(!participant->firefox)
- //~ janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
- //~ else
- //~ janus_rtcp_fir_legacy((char *)&buf, 20, &participant->fir_seq);
- JANUS_LOG(LOG_VERB, "Sending FIR to %s\n", participant->display);
- gateway->relay_rtcp(handle, video, buf, 20);
- /* Send a PLI too, just in case... */
- memset(buf, 0, 12);
- janus_rtcp_pli((char *)&buf, 12);
- JANUS_LOG(LOG_VERB, "Sending PLI to %s\n", participant->display);
- gateway->relay_rtcp(handle, video, buf, 12);
- if(participant->firefox && participant->bitrate > 0) {
- /* Now that we're there, let's send a REMB as well */
- janus_rtcp_remb((char *)&buf, 24, participant->bitrate);
- gateway->relay_rtcp(handle, video, buf, 24);
- }
- }
- }
- }
- }
- void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
- if(handle == NULL || handle->stopped || stopping || !initialized || !gateway)
- return;
- janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
- if(!session || session->destroy || !session->participant || !video)
- return;
- if(session->participant_type == janus_videoroom_p_type_subscriber) {
- /* FIXME Badly: we're blinding forwarding the listener RTCP to the publisher: this probably means confusing him... */
- janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
- if(l && l->feed) {
- janus_videoroom_participant *p = l->feed;
- if(p && p->session) {
- if((!video && p->audio_active) || (video && p->video_active)) {
- if(p->bitrate > 0)
- janus_rtcp_cap_remb(buf, len, p->bitrate);
- gateway->relay_rtcp(p->session->handle, video, buf, len);
- }
- }
- }
- } else if(session->participant_type == janus_videoroom_p_type_publisher) {
- /* FIXME Badly: we're just bouncing the incoming RTCP back with modified REMB, we need to improve this... */
- janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
- if(participant && participant->session) {
- if((!video && participant->audio_active) || (video && participant->video_active)) {
- if(participant->bitrate > 0)
- janus_rtcp_cap_remb(buf, len, participant->bitrate);
- gateway->relay_rtcp(handle, video, buf, len);
- /* FIXME Badly: we're also blinding forwarding the publisher RTCP to all the listeners: this probably means confusing them... */
- if(participant->listeners != NULL) {
- GSList *ps = participant->listeners;
- while(ps) {
- janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
- if(l->session && l->session->handle) {
- gateway->relay_rtcp(l->session->handle, video, buf, len);
- }
- ps = ps->next;
- }
- }
- }
- }
- }
- }
- void janus_videoroom_hangup_media(janus_plugin_session *handle) {
- JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
- if(stopping || !initialized)
- return;
- janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
- if(!session) {
- JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
- return;
- }
- if(session->destroy)
- return;
- /* Send an event to the browser and tell it's over */
- if(session->participant_type == janus_videoroom_p_type_publisher) {
- /* Get rid of publisher */
- janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
- participant->audio_active = FALSE;
- participant->video_active = FALSE;
- json_t *event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "room", json_integer(participant->room->room_id));
- json_object_set_new(event, "leaving", json_integer(participant->user_id));
- char *leaving_text = json_dumps(event, JSON_INDENT(3));
- json_decref(event);
- g_hash_table_remove(participant->room->participants, GUINT_TO_POINTER(participant->user_id));
- GList *participants_list = g_hash_table_get_values(participant->room->participants);
- GList *ps = participants_list;
- while(ps) {
- janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
- if(p == participant) {
- ps = ps->next;
- continue; /* Skip the leaving publisher itself */
- }
- JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
- int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
- JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
- ps = ps->next;
- }
- g_free(leaving_text);
- g_list_free(participants_list);
- } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
- /* Get rid of listener */
- janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
- janus_videoroom_participant *publisher = listener->feed;
- if(publisher != NULL) {
- janus_mutex_lock(&publisher->listeners_mutex);
- publisher->listeners = g_slist_remove(publisher->listeners, listener);
- janus_mutex_unlock(&publisher->listeners_mutex);
- listener->feed = NULL;
- }
- }
- }
- /* Thread to handle incoming messages */
- static void *janus_videoroom_handler(void *data) {
- JANUS_LOG(LOG_VERB, "Joining thread\n");
- janus_videoroom_message *msg = NULL;
- int error_code = 0;
- char *error_cause = calloc(512, sizeof(char)); /* FIXME 512 should be enough, but anyway... */
- if(error_cause == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- return NULL;
- }
- while(initialized && !stopping) {
- if(!messages || (msg = g_queue_pop_head(messages)) == NULL) {
- usleep(50000);
- continue;
- }
- janus_videoroom_session *session = (janus_videoroom_session *)msg->handle->plugin_handle;
- if(!session) {
- JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
- janus_videoroom_message_free(msg);
- continue;
- }
- if(session->destroy) {
- janus_videoroom_message_free(msg);
- continue;
- }
- /* Handle request */
- error_code = 0;
- JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message);
- if(msg->message == NULL) {
- JANUS_LOG(LOG_ERR, "No message??\n");
- error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
- sprintf(error_cause, "%s", "No message??");
- goto error;
- }
- json_error_t error;
- json_t *root = json_loads(msg->message, 0, &error);
- if(!root) {
- JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
- sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text);
- goto error;
- }
- if(!json_is_object(root)) {
- JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
- sprintf(error_cause, "JSON error: not an object");
- goto error;
- }
- /* Get the request first */
- json_t *request = json_object_get(root, "request");
- if(!request) {
- JANUS_LOG(LOG_ERR, "Missing element (request)\n");
- error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
- sprintf(error_cause, "Missing element (request)");
- goto error;
- }
- if(!json_is_string(request)) {
- JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (request should be a string)");
- goto error;
- }
- const char *request_text = json_string_value(request);
- json_t *event = NULL;
- if(!strcasecmp(request_text, "create")) {
- /* Create a new videoroom */
- JANUS_LOG(LOG_VERB, "Creating a new videoroom\n");
- json_t *desc = json_object_get(root, "description");
- if(desc && !json_is_string(desc)) {
- JANUS_LOG(LOG_ERR, "Invalid element (description should be a string)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (description should be a string)");
- goto error;
- }
- json_t *bitrate = json_object_get(root, "bitrate");
- if(bitrate && !json_is_integer(bitrate)) {
- JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be an integer)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (bitrate should be an integer)");
- goto error;
- }
- json_t *fir_freq = json_object_get(root, "fir_freq");
- if(fir_freq && !json_is_integer(fir_freq)) {
- JANUS_LOG(LOG_ERR, "Invalid element (fir_freq should be an integer)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (fir_freq should be an integer)");
- goto error;
- }
- json_t *publishers = json_object_get(root, "publishers");
- if(publishers && !json_is_integer(publishers)) {
- JANUS_LOG(LOG_ERR, "Invalid element (publishers should be an integer)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (publishers should be an integer)");
- goto error;
- }
- /* Create the audio bridge room */
- janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
- if(videoroom == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
- sprintf(error_cause, "Memory error");
- goto error;
- }
- /* Generate a random ID */
- janus_mutex_lock(&rooms_mutex);
- guint64 room_id = 0;
- while(room_id == 0) {
- room_id = g_random_int();
- if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
- /* Room ID already taken, try another one */
- room_id = 0;
- }
- }
- videoroom->room_id = room_id;
- char *description = NULL;
- if(desc != NULL) {
- description = g_strdup(json_string_value(desc));
- } else {
- char roomname[255];
- sprintf(roomname, "Room %"SCNu64"", videoroom->room_id);
- description = g_strdup(roomname);
- }
- if(description == NULL) {
- janus_mutex_unlock(&rooms_mutex);
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- continue;
- }
- videoroom->room_name = description;
- videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
- if(publishers)
- videoroom->max_publishers = json_integer_value(publishers);
- if(videoroom->max_publishers < 0)
- videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
- videoroom->bitrate = 0;
- if(bitrate)
- videoroom->bitrate = json_integer_value(bitrate);
- if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
- videoroom->bitrate = 64000; /* Don't go below 64k */
- videoroom->fir_freq = 0;
- if(fir_freq)
- videoroom->fir_freq = json_integer_value(fir_freq);
- videoroom->destroy = 0;
- videoroom->participants = g_hash_table_new(NULL, NULL);
- g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
- JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
- /* Show updated rooms list */
- GList *rooms_list = g_hash_table_get_values(rooms);
- GList *r = rooms_list;
- while(r) {
- janus_videoroom *vr = (janus_videoroom *)r->data;
- JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
- r = r->next;
- }
- g_list_free(rooms_list);
- janus_mutex_unlock(&rooms_mutex);
- /* Send info back */
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("created"));
- json_object_set_new(event, "room", json_integer(videoroom->room_id));
- } else
- /* What kind of participant is this session referring to? */
- if(session->participant_type == janus_videoroom_p_type_none) {
- JANUS_LOG(LOG_VERB, "Configuring new participant\n");
- /* Not configured yet, we need to do this now */
- if(strcasecmp(request_text, "join")) {
- JANUS_LOG(LOG_ERR, "Invalid request on unconfigured participant\n");
- error_code = JANUS_VIDEOROOM_ERROR_JOIN_FIRST;
- sprintf(error_cause, "Invalid request on unconfigured participant");
- goto error;
- }
- json_t *room = json_object_get(root, "room");
- if(!room) {
- JANUS_LOG(LOG_ERR, "Missing element (room)\n");
- sprintf(error_cause, "Missing element (room)");
- goto error;
- }
- if(!json_is_integer(room)) {
- JANUS_LOG(LOG_ERR, "Invalid element (room should be an integer)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (room should be an integer)");
- goto error;
- }
- guint64 room_id = json_integer_value(room);
- janus_mutex_lock(&rooms_mutex);
- janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
- if(videoroom == NULL) {
- janus_mutex_unlock(&rooms_mutex);
- JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
- error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
- sprintf(error_cause, "No such room (%"SCNu64")", room_id);
- goto error;
- }
- janus_mutex_unlock(&rooms_mutex);
- json_t *ptype = json_object_get(root, "ptype");
- if(!ptype) {
- JANUS_LOG(LOG_ERR, "Missing element (ptype)\n");
- error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
- sprintf(error_cause, "Missing element (ptype)");
- goto error;
- }
- if(!json_is_string(ptype)) {
- JANUS_LOG(LOG_ERR, "Invalid element (ptype should be a string)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (ptype should be a string)");
- goto error;
- }
- const char *ptype_text = json_string_value(ptype);
- if(!strcasecmp(ptype_text, "publisher")) {
- JANUS_LOG(LOG_VERB, "Configuring new publisher\n");
- json_t *display = json_object_get(root, "display");
- if(!display) {
- JANUS_LOG(LOG_ERR, "Missing element (display)\n");
- error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
- sprintf(error_cause, "Missing element (display)");
- goto error;
- }
- if(!json_is_string(display)) {
- JANUS_LOG(LOG_ERR, "Invalid element (display should be a string)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (display should be a string)");
- goto error;
- }
- const char *display_text = json_string_value(display);
- /* Generate a random ID */
- guint64 user_id = 0;
- while(user_id == 0) {
- user_id = g_random_int();
- if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
- /* User ID already taken, try another one */
- user_id = 0;
- }
- }
- JANUS_LOG(LOG_VERB, " -- Publisher ID: %"SCNu64"\n", user_id);
- janus_videoroom_participant *publisher = calloc(1, sizeof(janus_videoroom_participant));
- if(publisher == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
- sprintf(error_cause, "Memory error");
- goto error;
- }
- publisher->session = session;
- publisher->room = videoroom;
- publisher->user_id = user_id;
- publisher->display = g_strdup(display_text);
- if(publisher->display == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
- sprintf(error_cause, "Memory error");
- g_free(publisher);
- goto error;
- }
- publisher->sdp = NULL; /* We'll deal with this later */
- publisher->audio_active = FALSE;
- publisher->video_active = FALSE;
- publisher->firefox = FALSE;
- publisher->bitrate = videoroom->bitrate;
- publisher->listeners = NULL;
- janus_mutex_init(&publisher->listeners_mutex);
- publisher->fir_latest = 0;
- publisher->fir_seq = 0;
- /* Done */
- session->participant_type = janus_videoroom_p_type_publisher;
- session->participant = publisher;
- g_hash_table_insert(videoroom->participants, GUINT_TO_POINTER(user_id), publisher);
- /* Return a list of all available publishers (those with an SDP available, that is) */
- json_t *list = json_array();
- GList *participants_list = g_hash_table_get_values(videoroom->participants);
- GList *ps = participants_list;
- while(ps) {
- janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
- if(p == publisher || !p->sdp) {
- ps = ps->next;
- continue;
- }
- json_t *pl = json_object();
- json_object_set_new(pl, "id", json_integer(p->user_id));
- json_object_set_new(pl, "display", json_string(p->display));
- json_array_append_new(list, pl);
- ps = ps->next;
- }
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("joined"));
- json_object_set_new(event, "room", json_integer(videoroom->room_id));
- json_object_set_new(event, "description", json_string(videoroom->room_name));
- json_object_set_new(event, "id", json_integer(user_id));
- json_object_set_new(event, "publishers", list);
- g_list_free(participants_list);
- } else if(!strcasecmp(ptype_text, "listener")) {
- JANUS_LOG(LOG_VERB, "Configuring new listener\n");
- /* This is a new listener */
- json_t *feed = json_object_get(root, "feed");
- if(!feed) {
- JANUS_LOG(LOG_ERR, "Missing element (feed)\n");
- error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
- sprintf(error_cause, "Missing element (feed)");
- goto error;
- }
- if(!json_is_integer(feed)) {
- JANUS_LOG(LOG_ERR, "Invalid element (feed should be an integer)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (feed should be an integer)");
- goto error;
- }
- guint64 feed_id = json_integer_value(feed);
- janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
- if(publisher == NULL || publisher->sdp == NULL) {
- JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
- error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
- sprintf(error_cause, "No such feed (%"SCNu64")", feed_id);
- goto error;
- } else {
- janus_videoroom_listener *listener = calloc(1, sizeof(janus_videoroom_listener));
- if(listener == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
- sprintf(error_cause, "Memory error");
- goto error;
- }
- listener->session = session;
- listener->room = videoroom;
- listener->feed = publisher;
- listener->paused = TRUE; /* We need an explicit start from the listener */
- session->participant = listener;
- janus_mutex_lock(&publisher->listeners_mutex);
- publisher->listeners = g_slist_append(publisher->listeners, listener);
- janus_mutex_unlock(&publisher->listeners_mutex);
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("attached"));
- json_object_set_new(event, "room", json_integer(videoroom->room_id));
- json_object_set_new(event, "id", json_integer(feed_id));
- json_object_set_new(event, "display", json_string(publisher->display));
- session->participant_type = janus_videoroom_p_type_subscriber;
- JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
- char *event_text = json_dumps(event, JSON_INDENT(3));
- json_decref(event);
- /* Negotiate by sending the selected publisher SDP back */
- if(publisher->sdp != NULL) {
- /* How long will the gateway take to push the event? */
- gint64 start = janus_get_monotonic_time();
- int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, "offer", publisher->sdp);
- JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
- JANUS_LOG(LOG_VERB, " >> %d\n", res);
- g_free(event_text);
- json_decref(root);
- continue;
- }
- g_free(event_text);
- }
- } else {
- JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid element (ptype)");
- goto error;
- }
- } else if(session->participant_type == janus_videoroom_p_type_publisher) {
- /* Handle this publisher */
- janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
- if(!strcasecmp(request_text, "join")) {
- JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
- error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
- sprintf(error_cause, "Already in as a publisher on this handle");
- goto error;
- } else if(!strcasecmp(request_text, "configure")) {
- /* Configure audio/video/bitrate for this publisher */
- json_t *audio = json_object_get(root, "audio");
- if(audio && !json_is_boolean(audio)) {
- JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid value (audio should be a boolean)");
- goto error;
- }
- json_t *video = json_object_get(root, "video");
- if(video && !json_is_boolean(video)) {
- JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid value (video should be a boolean)");
- goto error;
- }
- json_t *bitrate = json_object_get(root, "bitrate");
- if(bitrate && !json_is_integer(bitrate)) {
- JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be an integer)\n");
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
- sprintf(error_cause, "Invalid value (bitrate should be an integer)");
- goto error;
- }
- if(audio) {
- participant->audio_active = json_is_true(audio);
- JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", participant->audio_active ? "true" : "false", participant->room->room_id, participant->user_id);
- }
- if(video) {
- participant->video_active = json_is_true(video);
- JANUS_LOG(LOG_VERB, "Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", participant->video_active ? "true" : "false", participant->room->room_id, participant->user_id);
- }
- if(bitrate) {
- participant->bitrate = json_integer_value(bitrate);
- JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
- }
- /* Done */
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "room", json_integer(participant->room->room_id));
- json_object_set_new(event, "configured", json_string("ok"));
- } else if(!strcasecmp(request_text, "leave")) {
- /* This publisher is leaving, tell everybody */
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "room", json_integer(participant->room->room_id));
- json_object_set_new(event, "leaving", json_integer(participant->user_id));
- char *leaving_text = json_dumps(event, JSON_INDENT(3));
- GList *participants_list = g_hash_table_get_values(participant->room->participants);
- GList *ps = participants_list;
- while(ps) {
- janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
- if(p == participant) {
- ps = ps->next;
- continue; /* Skip the new publisher itself */
- }
- JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
- int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
- JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
- ps = ps->next;
- }
- g_free(leaving_text);
- g_list_free(participants_list);
- /* Done */
- participant->audio_active = 0;
- participant->video_active = 0;
- session->started = FALSE;
- session->destroy = TRUE;
- } else {
- JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
- sprintf(error_cause, "Unknown request '%s'", request_text);
- goto error;
- }
- } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
- /* Handle this listener */
- janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
- if(!strcasecmp(request_text, "join")) {
- JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
- error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
- sprintf(error_cause, "Already in as a listener on this handle");
- goto error;
- } else if(!strcasecmp(request_text, "start")) {
- /* Start/restart receiving the publisher streams */
- janus_videoroom_participant *publisher = listener->feed;
- listener->paused = FALSE;
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "room", json_integer(publisher->room->room_id));
- json_object_set_new(event, "started", json_string("ok"));
- /* Send a FIR */
- char buf[20];
- memset(buf, 0, 20);
- if(!publisher->firefox)
- janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
- else
- janus_rtcp_fir_legacy((char *)&buf, 20, &publisher->fir_seq);
- JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %s\n", publisher->display);
- gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
- /* Send a PLI too, just in case... */
- memset(buf, 0, 12);
- janus_rtcp_pli((char *)&buf, 12);
- JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %s\n", publisher->display);
- gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
- } else if(!strcasecmp(request_text, "pause")) {
- /* Stop receiving the publisher streams for a while */
- janus_videoroom_participant *publisher = listener->feed;
- listener->paused = TRUE;
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "room", json_integer(publisher->room->room_id));
- json_object_set_new(event, "paused", json_string("ok"));
- } else if(!strcasecmp(request_text, "leave")) {
- janus_videoroom_participant *publisher = listener->feed;
- if(publisher != NULL) {
- janus_mutex_lock(&publisher->listeners_mutex);
- publisher->listeners = g_slist_remove(publisher->listeners, listener);
- janus_mutex_unlock(&publisher->listeners_mutex);
- listener->feed = NULL;
- }
- event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "room", json_integer(publisher->room->room_id));
- json_object_set_new(event, "left", json_string("ok"));
- session->started = FALSE;
- } else {
- JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
- sprintf(error_cause, "Unknown request '%s'", request_text);
- goto error;
- }
- }
- json_decref(root);
- /* Prepare JSON event */
- JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
- char *event_text = json_dumps(event, JSON_INDENT(3));
- json_decref(event);
- /* Any SDP to handle? */
- if(!msg->sdp) {
- int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
- JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
- } else {
- JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
- char *type = NULL;
- if(!strcasecmp(msg->sdp_type, "offer")) {
- /* We need to answer */
- type = "answer";
- } else if(!strcasecmp(msg->sdp_type, "answer")) {
- /* We got an answer (from a listener?), no need to negotiate */
- int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
- JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
- } else {
- /* TODO We don't support anything else right now... */
- JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg->sdp_type);
- error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
- sprintf(error_cause, "Unknown SDP type '%s'", msg->sdp_type);
- goto error;
- }
- if(session->participant_type == janus_videoroom_p_type_publisher) {
- /* This is a new publisher: is there room? */
- janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
- janus_videoroom *videoroom = participant->room;
- GList *participants_list = g_hash_table_get_values(videoroom->participants);
- GList *ps = participants_list;
- int count = 0;
- while(ps) {
- janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
- if(p != participant && p->sdp)
- count++;
- ps = ps->next;
- }
- if(count == videoroom->max_publishers) {
- g_list_free(participants_list);
- participant->audio_active = FALSE;
- participant->video_active = FALSE;
- JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
- error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
- sprintf(error_cause, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
- goto error;
- }
- /* Now prepare the SDP to give back */
- if(strstr(msg->sdp, "Mozilla")) {
- participant->firefox = TRUE;
- }
- /* Which media are available? */
- int audio = 0, video = 0;
- if(strstr(msg->sdp, "m=audio")) {
- audio++;
- }
- JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", audio ? "is" : "is NOT");
- if(strstr(msg->sdp, "m=video")) {
- video++;
- }
- JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", video ? "is" : "is NOT");
- /* What is the Opus payload type? */
- int opus_pt = 0;
- if(audio) {
- char *fmtp = strstr(msg->sdp, "opus/48000");
- if(fmtp != NULL) {
- fmtp -= 5;
- fmtp = strstr(fmtp, ":");
- if(fmtp)
- fmtp++;
- opus_pt = atoi(fmtp);
- }
- JANUS_LOG(LOG_VERB, "Opus payload type is %d\n", opus_pt);
- }
- /* What is the VP8 payload type? */
- int vp8_pt = 0;
- if(video) {
- char *fmtp = strstr(msg->sdp, "VP8/90000");
- if(fmtp != NULL) {
- fmtp -= 5;
- fmtp = strstr(fmtp, ":");
- if(fmtp)
- fmtp++;
- vp8_pt = atoi(fmtp);
- }
- JANUS_LOG(LOG_VERB, "VP8 payload type is %d\n", vp8_pt);
- }
- /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
- int b = (int)(videoroom->bitrate/1000);
- char sdp[1024], audio_mline[256], video_mline[512];
- if(audio) {
- g_sprintf(audio_mline, sdp_a_template,
- opus_pt, /* Opus payload type */
- "recvonly", /* The publisher gets a recvonly back */
- opus_pt); /* Opus payload type */
- } else {
- audio_mline[0] = '\0';
- }
- if(video) {
- g_sprintf(video_mline, sdp_v_template,
- vp8_pt, /* VP8 payload type */
- b, /* Bandwidth */
- "recvonly", /* The publisher gets a recvonly back */
- vp8_pt, /* VP8 payload type */
- vp8_pt, /* VP8 payload type */
- vp8_pt, /* VP8 payload type */
- vp8_pt, /* VP8 payload type */
- vp8_pt); /* VP8 payload type */
- } else {
- audio_mline[0] = '\0';
- }
- g_sprintf(sdp, sdp_template,
- janus_get_monotonic_time(), /* We need current time here */
- janus_get_monotonic_time(), /* We need current time here */
- participant->room->room_name, /* Video room name */
- audio_mline, /* Audio m-line, if any */
- video_mline); /* Video m-line, if any */
- int modified = 0;
- char *newsdp = g_strdup(sdp), *tempsdp = NULL;
- if(video && b == 0) {
- /* Remove useless bandwidth attribute */
- tempsdp = string_replace(newsdp, "b=AS:0\r\n", "", &modified);
- if(modified) {
- g_free(newsdp);
- newsdp = tempsdp;
- }
- }
- JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, newsdp);
- /* How long will the gateway take to push the event? */
- gint64 start = janus_get_monotonic_time();
- int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, newsdp);
- JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
- tempsdp = string_replace(newsdp, "recvonly", "sendonly", &modified);
- if(modified) {
- g_free(newsdp);
- tempsdp = tempsdp;
- }
- if(res != JANUS_OK) {
- /* TODO Failed to negotiate? We should remove this publisher */
- } else {
- /* Store the participant's SDP for interested listeners */
- participant->sdp = newsdp;
- /* Notify all other participants that there's a new boy in town */
- json_t *list = json_array();
- json_t *pl = json_object();
- json_object_set_new(pl, "id", json_integer(participant->user_id));
- json_object_set_new(pl, "display", json_string(participant->display));
- json_array_append_new(list, pl);
- json_t *pub = json_object();
- json_object_set_new(pub, "videoroom", json_string("event"));
- json_object_set_new(pub, "room", json_integer(participant->room->room_id));
- json_object_set_new(pub, "publishers", list);
- char *pub_text = json_dumps(pub, JSON_INDENT(3));
- json_decref(pub);
- GList *participants_list = g_hash_table_get_values(participant->room->participants);
- GList *ps = participants_list;
- while(ps) {
- janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
- if(p == participant) {
- ps = ps->next;
- continue; /* Skip the new publisher itself */
- }
- JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
- int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub_text, NULL, NULL);
- JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
- ps = ps->next;
- }
- g_list_free(participants_list);
- /* Let's wait for the setup_media event */
- }
- } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
- /* Negotiate by sending the selected publisher SDP back */
- janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
- /* FIXME We should handle the case where the participant has no SDP... */
- if(listener != NULL) {
- janus_videoroom_participant *feed = (janus_videoroom_participant *)listener->feed;
- if(feed != NULL && feed->sdp != NULL) {
- /* How long will the gateway take to push the event? */
- gint64 start = janus_get_monotonic_time();
- int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, feed->sdp);
- JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
- if(res != JANUS_OK) {
- /* TODO Failed to negotiate? We should remove this listener */
- } else {
- /* Let's wait for the setup_media event */
- }
- }
- }
- }
- }
- g_free(event_text);
- janus_videoroom_message_free(msg);
- continue;
-
- error:
- {
- if(root != NULL)
- json_decref(root);
- /* Prepare JSON error event */
- json_t *event = json_object();
- json_object_set_new(event, "videoroom", json_string("event"));
- json_object_set_new(event, "error_code", json_integer(error_code));
- json_object_set_new(event, "error", json_string(error_cause));
- char *event_text = json_dumps(event, JSON_INDENT(3));
- json_decref(event);
- JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
- int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
- JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
- g_free(event_text);
- janus_videoroom_message_free(msg);
- }
- }
- g_free(error_cause);
- JANUS_LOG(LOG_VERB, "Leaving thread\n");
- return NULL;
- }
- static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
- janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
- if(!packet || !packet->data || packet->length < 1) {
- JANUS_LOG(LOG_ERR, "Invalid packet...\n");
- return;
- }
- janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
- if(!listener || !listener->session) {
- // JANUS_LOG(LOG_ERR, "Invalid session...\n");
- return;
- }
- if(listener->paused) {
- // JANUS_LOG(LOG_ERR, "This listener paused the stream...\n");
- return;
- }
- janus_videoroom_session *session = listener->session;
- if(!session || !session->handle) {
- // JANUS_LOG(LOG_ERR, "Invalid session...\n");
- return;
- }
- if(!session->started) {
- // JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
- return;
- }
- if(gateway != NULL) /* FIXME What about RTCP? */
- gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
- return;
- }
- /* Easy way to replace multiple occurrences of a string with another: ALWAYS creates a NEW string */
- char *string_replace(char *message, char *old, char *new, int *modified)
- {
- if(!message || !old || !new || !modified)
- return NULL;
- *modified = 0;
- if(!strstr(message, old)) { /* Nothing to be done (old is not there) */
- return message;
- }
- if(!strcmp(old, new)) { /* Nothing to be done (old=new) */
- return message;
- }
- if(strlen(old) == strlen(new)) { /* Just overwrite */
- char *outgoing = message;
- char *pos = strstr(outgoing, old), *tmp = NULL;
- int i = 0;
- while(pos) {
- i++;
- memcpy(pos, new, strlen(new));
- pos += strlen(old);
- tmp = strstr(pos, old);
- pos = tmp;
- }
- return outgoing;
- } else { /* We need to resize */
- *modified = 1;
- char *outgoing = strdup(message);
- if(outgoing == NULL) {
- JANUS_LOG(LOG_FATAL, "Memory error!\n");
- return NULL;
- }
- int diff = strlen(new) - strlen(old);
- /* Count occurrences */
- int counter = 0;
- char *pos = strstr(outgoing, old), *tmp = NULL;
- while(pos) {
- counter++;
- pos += strlen(old);
- tmp = strstr(pos, old);
- pos = tmp;
- }
- uint16_t oldlen = strlen(outgoing)+1, newlen = oldlen + diff*counter;
- *modified = diff*counter;
- if(diff > 0) { /* Resize now */
- tmp = realloc(outgoing, newlen);
- if(!tmp)
- return NULL;
- outgoing = tmp;
- }
- /* Replace string */
- pos = strstr(outgoing, old);
- while(pos) {
- if(diff > 0) { /* Move to the right (new is larger than old) */
- uint16_t len = strlen(pos)+1;
- memmove(pos + diff, pos, len);
- memcpy(pos, new, strlen(new));
- pos += strlen(new);
- tmp = strstr(pos, old);
- } else { /* Move to the left (new is smaller than old) */
- uint16_t len = strlen(pos - diff)+1;
- memmove(pos, pos - diff, len);
- memcpy(pos, new, strlen(new));
- pos += strlen(old);
- tmp = strstr(pos, old);
- }
- pos = tmp;
- }
- if(diff < 0) { /* We skipped the resize previously (shrinking memory) */
- tmp = realloc(outgoing, newlen);
- if(!tmp)
- return NULL;
- outgoing = tmp;
- }
- outgoing[strlen(outgoing)] = '\0';
- return outgoing;
- }
- }