/*! \file   janus_streaming.c
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
 * \copyright GNU General Public License v3
 * \brief  Janus Streaming plugin
 * \details Check the \ref streaming for more details.
 *
 * \ingroup plugins
 * \ref plugins
 *
 * \page streaming Streaming plugin documentation
 * This is a streaming plugin for Janus, allowing WebRTC peers
 * to watch/listen to pre-recorded files or media generated by another tool.
 * Specifically, the plugin currently supports three different type of streams:
 *
 * -# on-demand streaming of pre-recorded media files (different
 * streaming context for each peer);
 * -# live streaming of pre-recorded media files (shared streaming
 * context for all peers attached to the stream);
 * -# live streaming of media generated by another tool (shared
 * streaming context for all peers attached to the stream).
 *
 * For what concerns types 1. and 2., considering the proof of concept
 * nature of the implementation the only pre-recorded media files
 * that the plugins supports right now are raw mu-Law and a-Law files:
 * support is of course planned for other additional widespread formats
 * as well.
 *
 * For what concerns type 3., instead, the plugin is configured
 * to listen on a couple of ports for RTP: this means that the plugin
 * is implemented to receive RTP on those ports and relay them to all
 * peers attached to that stream. Any tool that can generate audio/video
 * RTP streams and specify a destination is good for the purpose: the
 * examples section contains samples that make use of GStreamer (http://gstreamer.freedesktop.org/)
 * but other tools like FFmpeg (http://www.ffmpeg.org/), LibAV (http://libav.org/)
 * or others are fine as well. This makes it really easy to capture and
 * encode whatever you want using your favourite tool, and then have it
 * transparently broadcasted via WebRTC using Janus. Notice that we recently
 * added  the possibility to also add a datachannel track to an RTP streaming
 * mountpoint: this allows you to send, via UDP, a text-based message to
 * relay via datachannels (e.g., the title of the current song, if this
 * is a radio streaming channel). When using this feature, though, beware
 * that you'll have to stay within the boundaries of the MTU, as each
 * message will have to stay within the size of an UDP packet.
 *
 * Streams to make available are listed in the plugin configuration file.
 * A pre-filled configuration file is provided in \c conf/janus.plugin.streaming.jcfg
 * and includes a stream of every type.
 *
 * To add more streams or modify the existing ones, you can use the following
 * syntax:
 *
 * \verbatim
stream-name: {
	[settings]
}
\endverbatim
 *
 * with the allowed settings listed below:
 *
 * \verbatim
type = rtp|live|ondemand|rtsp
       rtp = stream originated by an external tool (e.g., gstreamer or
             ffmpeg) and sent to the plugin via RTP
       live = local file streamed live to multiple viewers
              (multiple viewers = same streaming context)
       ondemand = local file streamed on-demand to a single listener
                  (multiple viewers = different streaming contexts)
       rtsp = stream originated by an external RTSP feed (only
              available if libcurl support was compiled)
id = <unique numeric ID>
description = This is my awesome stream
is_private = true|false (private streams don't appear when you do a 'list' request)
filename = path to the local file to stream (only for live/ondemand)
secret = <optional password needed for manipulating (e.g., destroying
		or enabling/disabling) the stream>
pin = <optional password needed for watching the stream>
audio = true|false (do/don't stream audio)
video = true|false (do/don't stream video)
   The following options are only valid for the 'rtp' type:
data = true|false (do/don't stream text via datachannels)
audioport = local port for receiving audio frames
audiortcpport = local port for receiving and sending audio RTCP feedback
audiomcast = multicast group for receiving audio frames, if any
audioiface = network interface or IP address to bind to, if any (binds to all otherwise)
audiopt = <audio RTP payload type> (e.g., 111)
audiortpmap = RTP map of the audio codec (e.g., opus/48000/2)
audiofmtp = Codec specific parameters, if any
audioskew = true|false (whether the plugin should perform skew
	analisys and compensation on incoming audio RTP stream, EXPERIMENTAL)
videoport = local port for receiving video frames (only for rtp)
videortcpport = local port for receiving and sending video RTCP feedback
videomcast = multicast group for receiving video frames, if any
videoiface = network interface or IP address to bind to, if any (binds to all otherwise)
videopt = <video RTP payload type> (e.g., 100)
videortpmap = RTP map of the video codec (e.g., VP8/90000)
videofmtp = Codec specific parameters, if any
videobufferkf = true|false (whether the plugin should store the latest
	keyframe and send it immediately for new viewers, EXPERIMENTAL)
videosimulcast = true|false (do|don't enable video simulcasting)
videoport2 = second local port for receiving video frames (only for rtp, and simulcasting)
videoport3 = third local port for receiving video frames (only for rtp, and simulcasting)
videoskew = true|false (whether the plugin should perform skew
	analisys and compensation on incoming video RTP stream, EXPERIMENTAL)
videosvc = true|false (whether the video will have SVC support; works only for VP9-SVC, default=false)
collision = in case of collision (more than one SSRC hitting the same port), the plugin
	will discard incoming RTP packets with a new SSRC unless this many milliseconds
	passed, which would then change the current SSRC (0=disabled)
dataport = local port for receiving data messages to relay
dataiface = network interface or IP address to bind to, if any (binds to all otherwise)
databuffermsg = true|false (whether the plugin should store the latest
	message and send it immediately for new viewers)
threads = number of threads to assist with the relaying part, which can help
	if you expect a lot of viewers that may cause the RTP receiving part
	in the Streaming plugin to slow down and fail to catch up (default=0)

In case you want to use SRTP for your RTP-based mountpoint, you'll need
to configure the SRTP-related properties as well, namely the suite to
use for hashing (32 or 80) and the crypto information for decrypting
the stream (as a base64 encoded string the way SDES does it). Notice
that with SRTP involved you'll have to pay extra attention to what you
feed the mountpoint, as you may risk getting SRTP decrypt errors:
srtpsuite = 32
srtpcrypto = WbTBosdVUZqEb6Htqhn+m3z7wUh4RJVR8nE15GbN

The following options are only valid for the 'rstp' type:
url = RTSP stream URL
rtsp_user = RTSP authorization username, if needed
rtsp_pwd = RTSP authorization password, if needed
rtsp_failcheck = whether an error should be returned if connecting to the RTSP server fails (default=true)
rtspiface = network interface IP address or device name to listen on when receiving RTSP streams
\endverbatim
 *
 * \section streamapi Streaming API
 *
 * The Streaming API supports several requests, some of which are
 * synchronous and some asynchronous. There are some situations, though,
 * (invalid JSON, invalid request) which will always result in a
 * synchronous error response even for asynchronous requests.
 *
 * \c list , \c info , \c create , \c destroy , \c recording , \c edit ,
 * \c enable and \c disable are synchronous requests, which means you'll
 * get a response directly within the context of the transaction. \c list
 * lists all the available streams; \c create allows you to create a new
 * mountpoint dynamically, as an alternative to using the configuration
 * file; \c destroy removes a mountpoint and destroys it; \c recording
 * instructs the plugin on whether or not a live RTP stream should be
 * recorded while it's broadcasted; \c enable and \c disable respectively
 * enable and disable a mountpoint, that is decide whether or not a
 * mountpoint should be available to users without destroying it.
 * \c edit allows you to dynamically edit some mountpoint properties (e.g., the PIN);
 *
 * The \c watch , \c start , \c configure , \c pause , \c switch and \c stop requests
 * instead are all asynchronous, which means you'll get a notification
 * about their success or failure in an event. \c watch asks the plugin
 * to prepare the playout of one of the available streams; \c start
 * starts the actual playout; \c pause allows you to pause a playout
 * without tearing down the PeerConnection; \c switch allows you to
 * switch to a different mountpoint of the same kind (note: only live
 * RTP mountpoints supported as of now) without having to stop and watch
 * the new one; \c stop stops the playout and tears the PeerConnection
 * down.
 *
 * Notice that, in general, all users can create mountpoints, no matter
 * what type they are. If you want to limit this functionality, you can
 * configure an admin \c admin_key in the plugin settings. When
 * configured, only "create" requests that include the correct
 * \c admin_key value in an "admin_key" property will succeed, and will
 * be rejected otherwise.
 *
 * \subsection streamingsync Synchronous requests
 *
 * To list the available Streaming mountpoints (both those created via
 * configuration file and those created via API), you can use the \c list
 * request:
 *
\verbatim
{
	"request" : "list"
}
\endverbatim
 *
 * If successful, it will return an array with a list of all the mountpoints.
 * Notice that only the public mountpoints will be returned: those with
 * an \c is_private set to yes/true will be skipped. The response will
 * be formatted like this:
 *
\verbatim
{
	"streaming" : "list",
	"list" : [
		{
			"id" : <unique ID of mountpoint #1>,
			"description" : "<description of mountpoint #1>",
			"type" : "<type of mountpoint #1, in line with the types introduced above>",
			"audio_age_ms" : <how much time passed since we last received audio; optional, available for RTP mountpoints only>,
			"video_age_ms" : <how much time passed since we last received video; optional, available for RTP mountpoints only>
		},
		{
			"id" : <unique ID of mountpoint #2>,
			"description" : "<description of mountpoint #2>",
			"type" : "<type of mountpoint #2, in line with the types introduced above>",
			"audio_age_ms" : <how much time passed since we last received audio; optional, available for RTP mountpoints only>,
			"video_age_ms" : <how much time passed since we last received video; optional, available for RTP mountpoints only>
		},
		...
	]
}
\endverbatim
 *
 * As you can see, the \c list request only returns very generic info on
 * each mounpoint. In case you're interested in learning more details about
 * a specific mountpoint, you can use the \c info request instead, which
 * returns more information, or all of it if the mountpoint secret is
 * provided in the request. An \c info request must be formatted like this:
 *
\verbatim
{
	"request" : "info"
	"id" : <unique ID of mountpoint to query>,
	"secret" : <mountpoint secret; optional, can be used to return more info>"
}
\endverbatim
 *
 * If successful, this will have the plugin return an object containing
 * more info on the mountpoint:
 *
\verbatim
{
	"streaming" : "info",
	"info" : {
		"id" : <unique ID of mountpoint>,
		"name" : "<unique name of mountpoint>",
		"description" : "<description of mountpoint>",
		"secret" : "<secret of mountpoint; only available if a valid secret was provided>",
		"pin" : "<PIN to access mountpoint; only available if a valid secret was provided>",
		"is_private" : <true|false, depending on whether the mountpoint is listable; only available if a valid secret was provided>,
		"enabled" : <true|false, depending on whether the mountpoint is currently enabled or not>,
		"audio" : <true, only present if the mountpoint contains audio>,
		"audiopt" : <audio payload type, only present if configured and the mountpoint contains audio>,
		"audiortpmap" : "<audio SDP rtpmap value, only present if configured and the mountpoint contains audio>",
		"audiofmtp" : "<audio SDP fmtp value, only present if configured and the mountpoint contains audio>",
		"video" : <true, only present if the mountpoint contains video>,
		"videopt" : <video payload type, only present if configured and the mountpoint contains video>,
		"videortpmap" : "<video SDP rtpmap value, only present if configured and the mountpoint contains video>",
		"videofmtp" : "<video SDP fmtp value, only present if configured and the mountpoint contains video>",
		...
	}
}
\endverbatim
 *
 * Considering the different mountpoint types that you can create in this
 * plugin, the nature of the rest of the returned info obviously depends
 * on which mountpoint you're querying. This is especially true for RTP
 * and RTSP mountpoints. Notice that info like the ports an RTP mountpoint
 * is listening on will only be returned if you provide the correct secret,
 * as otherwise they're treated like sensitive information and are not
 * returned to generic \c info calls.
 *
 * We've seen how you can create a new mountpoint via configuration file,
 * but you can create one via API as well, using the \c create request.
 * Most importantly, you can also choose whether or not a \c create
 * request should result in the mountpoint being saved to configuration
 * file so that it's still available after a server restart. The common
 * syntax for all \c create requests is the following:
 *
\verbatim
{
	"request" : "create",
	"admin_key" : "<plugin administrator key; mandatory if configured>",
	"type" : "<type of the mountpoint to create; mandatory>",
	"id" : <unique ID to assign the mountpoint; optional, will be chosen by the server if missing>,
	"name" : "<unique name for the mountpoint; optional, will be chosen by the server if missing>",
	"description" : "<description of mountpoint; optional>",
	"secret" : "<secret to query/edit the mountpoint later; optional>",
	"pin" : "<PIN required for viewers to access mountpoint; optional>",
	"is_private" : <true|false, whether the mountpoint should be listable; true by default>,
	"audio" : <true|false, whether the mountpoint will have audio; false by default>,
	"video" : <true|false, whether the mountpoint will have video; false by default>,
	"data" : <true|false, whether the mountpoint will have datachannels; false by default>,
	"permanent" : <true|false, whether the mountpoint should be saved to configuration file or not; false by default>,
	...
}
\endverbatim
 *
 * Of course, different mountpoint types will have different properties
 * you can specify in a \c create. Please refer to the documentation on
 * configuration files to see the fields you can pass. The only important
 * difference to highlight is that, unlike in configuration files, you will
 * NOT have to escape semicolons with a trailing slash, in those properties
 * where a semicolon might be needed (e.g., \c audiofmtp or \c videofmtp ).
 *
 * A successful \c create will result in a \c created response:
 *
\verbatim
{
	"streaming" : "created",
	"create" : "<unique name of the just created mountpoint>",
	"permanent" : <true|false, depending on whether the mountpoint was saved to configuration file or not>,
	"stream": {
		"id" : <unique ID of the just created mountpoint>,
		"type" : "<type of the just created mountpoint>",
		"description" : "<description of the just created mountpoint>",
		"is_private" : <true|false, depending on whether the new mountpoint is listable>,
		...
	}
}
\endverbatim
 *
 * Notice that additional information, namely the ports the mountpoint
 * bound to, will only be added for new RTP mountpoints, otherwise this
 * is all that a \c created request will contain. If you want to double
 * check everything in your \c create request went as expected, you may
 * want to issue a followup \c info request to compare the results.
 *
 * Once you created a mountpoint, you can modify some (not all) of its
 * properties via an \c edit request. Namely, you can only modify generic
 * properties like the mountoint description, the secret, the PIN and
 * whether or not the mountpoint should be listable. All other properties
 * are considered to be immutable. Again, you can choose whether the changes
 * should be permanent, e.g., saved to configuration file, or not. Notice
 * that an \c edit request requires the right secret to be provided, if
 * the mountpoint has one, or will return an error instead. The \c edit
 * request must be formatted like this:
 *
\verbatim
{
	"request" : "edit",
	"id" : <unique ID of the mountpoint to edit; mandatory>,
	"secret" : "<secret to edit the mountpoint; mandatory if configured>",
	"new_description" : "<new description for the mountpoint; optional>",
	"new_secret" : "<new secret for the mountpoint; optional>",
	"new_pin" : "<new PIN for the mountpoint; optional>",
	"new_is_private" : <true|false, depending on whether the mountpoint should be now listable; optional>,
	"permanent" : <true|false, whether the mountpoint should be saved to configuration file or not; false by default>
}
\endverbatim
 *
 * A successful \c edit will result in an \c edited response:
 *
\verbatim
{
	"streaming" : "edited",
	"id" : <unique ID of the just edited mountpoint>,
	"permanent" : <true|false, depending on whether the changes were saved to configuration file or not>
}
\endverbatim
 *
 * Just as you can create and edit mountpoints, you can of course also destroy
 * them. Again, this applies to all mountpoints, whether created statically
 * via configuration file or dynamically via API, and the mountpoint destruction
 * can be made permanent in the configuration file as well. A \c destroy
 * request must be formatted as follows:
 *
\verbatim
{
	"request" : "destroy",
	"id" : <unique ID of the mountpoint to destroy; mandatory>,
	"secret" : "<secret to destroy the mountpoint; mandatory if configured>",
	"permanent" : <true|false, whether the mountpoint should be removed from the configuration file or not; false by default>
}
\endverbatim
 *
 * If successful, the result will be confirmed in a \c destroyed event:
 *
\verbatim
{
	"streaming" : "destroyed",
	"id" : <unique ID of the just destroyed mountpoint>
}
\endverbatim
 *
 * Notice that destroying a mountpoint while viewers are still subscribed
 * to it will result in all viewers being removed, and their PeerConnection
 * closed as a consequence.
 *
 * You can also dynamically enable and disable mountpoints via API. A
 * disabled mountpoint is a mountpoint that exists, and still works as
 * expected, but is not accessible to viewers until it's enabled again.
 * This is a useful property, especially in case of mountpoints that
 * need to be prepared in advance but must not be accessible until a
 * specific moment, and a much better alternative to just create the
 * mountpoint at the very last minute and destroy it otherwise. The
 * syntax for both the \c enable and \c disable requests is the same,
 * and looks like the following:
 *
\verbatim
{
	"request" : "enable|disable",
	"id" : <unique ID of the mountpoint to enable/disable; mandatory>,
	"secret" : "<secret to enable/disable the mountpoint; mandatory if configured>"
}
\endverbatim
 *
 * In both cases, a generic \c ok is returned if successful:
 *
\verbatim
{
	"streaming" : "ok"
}
\endverbatim
 *
 * Finally, you can record a mountpoint to the internal Janus .mjr format
 * using the \c recording request. The same request can also be used to
 * stop recording. Although the same request is used in both cases, though,
 * the syntax for the two use cases differs a bit, namely in terms of the
 * type of some properties.
 *
 * To start recording a new mountpoint, the request should be formatted
 * like this:
 *
\verbatim
{
	"request" : "recording",
	"action" : "start",
	"id" : <unique ID of the mountpoint to manipulate; mandatory>,
	"audio" : "<enable audio recording, and use this base path/filename; optional>",
	"video" : "<enable video recording, and use this base path/filename; optional>",
	"data" : "<enable data recording, and use this base path/filename; optional>",
	"audio" : <true|false; whether or not audio should be recorded>,
	"video" : <true|false; whether or not video should be recorded>,
	"data" : <true|false; whether or not datachannel messages should be recorded>
}
\endverbatim
 *
 * To stop a recording, instead, this is the request syntax:
 *
\verbatim
{
	"request" : "recording",
	"action" : "stop",
	"id" : <unique ID of the mountpoint to manipulate; mandatory>,
	"audio" : <true|false; whether or not audio recording should be stopped>,
	"video" : <true|false; whether or not video recording should be stopped>,
	"data" : <true|false; whether or not datachannel recording should be stopped>
}
\endverbatim
 *
 * As you can notice, when you want to start a recording the \c audio ,
 * \c video and \c data properties are strings, and specify the base path
 * to use for the recording filename; when stopping a recording, instead,
 * they're interpreted as boolean properties. Notice that, as with all
 * APIs that wrap .mjr recordings, the filename you specify here is not
 * the actual filename: an \c .mjr extension is always going to be added
 * by the Janus core, so you should take this into account when tracking
 * the related recording files.
 *
 * Whether you started or stopped a recording, a successful request will
 * always result in a simple \c ok response:
 *
\verbatim
{
	"streaming" : "ok"
}
\endverbatim
 *
 * \subsection streamingasync Asynchronous requests
 *
 * All the requests we've gone through so far are synchronous. This means
 * that they return a response right away. That said, many of the requests
 * this plugin supports are asynchronous instead, which means Janus will
 * send an ack when they're received, and a response will only follow
 * later on. This is especially true for requests dealing with the
 * management and setup of mountpoint viewers, e.g., for the purpose of
 * negotiating a WebRTC PeerConnection to receive media from a mountpoint.
 *
 * To subscribe to a specific mountpoint, an interested viewer can make
 * use of the \c watch request. As suggested by the request name, this
 * instructs the plugin to setup a new PeerConnection to allow the new
 * viewer to watch the specified mountpoint. The \c watch request must
 * be formatted like this:
 *
\verbatim
{
	"request" : "watch",
	"id" : <unique ID of the mountpoint to subscribe to; mandatory>,
	"pin" : "<PIN required to access the mountpoint; mandatory if configured>",
	"offer_audio" : <true|false; whether or not audio should be negotiated; true by default if the mountpoint has audio>,
	"offer_video" : <true|false; whether or not video should be negotiated; true by default if the mountpoint has video>,
	"offer_data" : <true|false; whether or not datachannels should be negotiated; true by default if the mountpoint has datachannels>
}
\endverbatim
 *
 * As you can see, it's just a matter of specifying the ID of the mountpoint to
 * subscribe to and, if needed, the PIN to access the mountpoint in case
 * it's protected. The \c offer_audio , \c offer_video and \c offer_data are
 * also particularly interesting, though, as they allow you to only subscribe
 * to a subset of the mountpoint media. By default, in fact, a \c watch
 * request will result in the plugin preparing a new SDP offer trying to
 * negotiate all the media streams available in the mountpoint; in case
 * the viewer knows they don't support one of the mountpoint codecs, though
 * (e.g., the video in the mountpoint is VP8, but they only support H.264),
 * or are not interested in getting all the media (e.g., they're ok with
 * just audio and not video, or don't have enough bandwidth for both),
 * they can use those properties to shape the SDP offer to their needs.
 *
 * As anticipated, if successful this request will generate a new JSEP SDP
 * offer, which will be attached to a \c preparing status event:
 *
\verbatim
{
	"status" : "preparing"
}
\endverbatim
 *
 * At this stage, to complete the setup of a subscription the viewer is
 * supposed to send a JSEP SDP answer back to the plugin. This is done
 * by means of a \c start request, which in this case MUST be associated
 * with a JSEP SDP answer but otherwise requires no arguments:
 *
\verbatim
{
	"request" : "start"
}
\endverbatim
 *
 * If successful this request returns a \c starting status event:
 *
\verbatim
{
	"status" : "starting"
}
\endverbatim
 *
 * Once this is done, all that's needed is waiting for the WebRTC PeerConnection
 * establishment to succeed. As soon as that happens, the Streaming plugin
 * can start relaying media from the mountpoint the viewer subscribed to
 * to the viewer themselves.
 *
 * Notice that the same exact steps we just went through (\c watch request,
 * followed by JSEP offer by the plugin, followed by \c start request with
 * JSEP answer by the viewer) is what you also use when renegotiations are
 * needed, e.g., for the purpose of ICE restarts.
 *
 * As a viewer, you can temporarily pause and resume the whole media delivery
 * with a \c pause and, again, \c start request (in this case without any JSEP
 * SDP answer attached). Neither expect other arguments, as the context
 * is implicitly derived from the handle they're sent on:
 *
\verbatim
{
	"request" : "pause"
}
\endverbatim
 *
\verbatim
{
	"request" : "start"
}
\endverbatim
 *
 * Unsurprisingly, they just result in, respectively, \c pausing and
 * \c starting events:
 *
\verbatim
{
	"status" : "pausing"
}
\endverbatim
 *
\verbatim
{
	"status" : "starting"
}
\endverbatim
 *
 * For more drill-down manipulations of a subscription, a \c configure
 * request can be used instead. This request allows viewers to dynamically
 * change some properties associated to their media subscription, e.g.,
 * in terms of what should and should not be sent at a specific time. A
 * \c configure request must be formatted as follows:
 *
\verbatim
{
	"request" : "configure",
	"audio" : <true|false, depending on whether audio should be relayed or not; optional>,
	"video" : <true|false, depending on whether video should be relayed or not; optional>,
	"data" : <true|false, depending on whether datachannel messages should be relayed or not; optional>,
	"substream" : <substream to receive (0-2), in case simulcasting is enabled; optional>,
	"temporal" : <temporal layers to receive (0-2), in case simulcasting is enabled; optional>,
	"spatial_layer" : <spatial layer to receive (0-1), in case VP9-SVC is enabled; optional>,
	"temporal_layer" : <temporal layers to receive (0-2), in case VP9-SVC is enabled; optional>
}
\endverbatim
 *
 * As you can see, the \c audio , \c video and \c data properties can be
 * used as a media-level pause/resume functionality, whereas \c pause
 * and \c start simply pause and resume all streams at the same time.
 * The \c substream and \c temporal properties, instead, only make sense
 * when the mountpoint is configured with video simulcasting support, and
 * as such the viewer is interested in receiving a specific substream
 * or temporal layer, rather than any other of the available ones.
 * The \c spatial_layer and \c temporal_layer have exactly the same meaning,
 * but within the context of VP9-SVC mountpoints, and will have no effect
 * on mountpoints involving a different video codec.
 *
 * Another interesting feature in the Streaming plugin is the so-called
 * mountpoint "switching". Basically, when subscribed to a specific
 * mountpoint and receiving media from there, you can at any time "switch"
 * to a different mountpoint, and as such start receiving media from that
 * other mountpoint instead. Think of it as changing channel on a TV: you
 * keep on using the same PeerConnection, the plugin simply changes the
 * source of the media transparently. Of course, while powerful and effective
 * this request has some limitations. First of all, it only works with RTP
 * mountpoints, and not other mountpoint types; besides, the two mountpoints
 * must have the same media configuration, that is, use the same codecs,
 * the same payload types, etc. In fact, since the same PeerConnection is
 * used for this feature, switching to a mountpoint with a different
 * configuration might result in media incompatible with the PeerConnection
 * setup being relayed to the viewer, and as such in no audio/video being
 * played. That said, a \c switch request must be formatted like this:
 *
\verbatim
{
	"request" : "switch",
	"id" : <unique ID of the new mountpoint to switch to; mandatory>
}
\endverbatim
 *
 * If successful, you'll be unsubscribed from the previous mountpoint,
 * and subscribed to the new mountpoint instead. The event to confirm
 * the switch was successful will look like this:
 *
\verbatim
{
	"switched" : "ok",
	"id" : <unique ID of the new mountpoint>
}
\endverbatim
 *
 * Finally, to stop the subscription to the mountpoint and tear down the
 * related PeerConnection, you can use the \c stop request. Since context
 * is implicit, no other argument is required:
 *
\verbatim
{
	"request" : "stop"
}
\endverbatim
 *
 * If successful, the plugin will attempt to tear down the PeerConnection,
 * and will send back a \c stopping status event:
 *
\verbatim
{
	"status" : "stopping"
}
\endverbatim
 *
 * Once a PeerConnection has been torn down and the subscription closed,
 * as a viewer you're free to subscribe to a different mountpoint instead.
 * In fact, while you can't watch more than one mountpoint at the same
 * time on the same handle, there's no limit on how many mountpoints
 * you can watch in sequence, again on the same handle. If you're interested
 * in subscribing to multiple mountpoints at the same time, instead, you'll
 * have to create multiple handles for the purpose.
 */


#include "plugin.h"

#include <errno.h>
#include <netdb.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <jansson.h>

#ifdef HAVE_LIBCURL
#include <curl/curl.h>
#endif

#include "../debug.h"
#include "../apierror.h"
#include "../config.h"
#include "../mutex.h"
#include "../rtp.h"
#include "../rtpsrtp.h"
#include "../rtcp.h"
#include "../record.h"
#include "../utils.h"
#include "../ip-utils.h"


/* Plugin information */
#define JANUS_STREAMING_VERSION			8
#define JANUS_STREAMING_VERSION_STRING	"0.0.8"
#define JANUS_STREAMING_DESCRIPTION		"This is a streaming plugin for Janus, allowing WebRTC peers to watch/listen to pre-recorded files or media generated by an external source."
#define JANUS_STREAMING_NAME			"JANUS Streaming plugin"
#define JANUS_STREAMING_AUTHOR			"Meetecho s.r.l."
#define JANUS_STREAMING_PACKAGE			"janus.plugin.streaming"

/* Plugin methods */
janus_plugin *create(void);
int janus_streaming_init(janus_callbacks *callback, const char *config_path);
void janus_streaming_destroy(void);
int janus_streaming_get_api_compatibility(void);
int janus_streaming_get_version(void);
const char *janus_streaming_get_version_string(void);
const char *janus_streaming_get_description(void);
const char *janus_streaming_get_name(void);
const char *janus_streaming_get_author(void);
const char *janus_streaming_get_package(void);
void janus_streaming_create_session(janus_plugin_session *handle, int *error);
struct janus_plugin_result *janus_streaming_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
json_t *janus_streaming_handle_admin_message(json_t *message);
void janus_streaming_setup_media(janus_plugin_session *handle);
void janus_streaming_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
void janus_streaming_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
void janus_streaming_hangup_media(janus_plugin_session *handle);
void janus_streaming_destroy_session(janus_plugin_session *handle, int *error);
json_t *janus_streaming_query_session(janus_plugin_session *handle);
static int janus_streaming_get_fd_port(int fd);

/* Plugin setup */
static janus_plugin janus_streaming_plugin =
	JANUS_PLUGIN_INIT (
		.init = janus_streaming_init,
		.destroy = janus_streaming_destroy,

		.get_api_compatibility = janus_streaming_get_api_compatibility,
		.get_version = janus_streaming_get_version,
		.get_version_string = janus_streaming_get_version_string,
		.get_description = janus_streaming_get_description,
		.get_name = janus_streaming_get_name,
		.get_author = janus_streaming_get_author,
		.get_package = janus_streaming_get_package,

		.create_session = janus_streaming_create_session,
		.handle_message = janus_streaming_handle_message,
		.handle_admin_message = janus_streaming_handle_admin_message,
		.setup_media = janus_streaming_setup_media,
		.incoming_rtp = janus_streaming_incoming_rtp,
		.incoming_rtcp = janus_streaming_incoming_rtcp,
		.hangup_media = janus_streaming_hangup_media,
		.destroy_session = janus_streaming_destroy_session,
		.query_session = janus_streaming_query_session,
	);

/* Plugin creator */
janus_plugin *create(void) {
	JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_STREAMING_NAME);
	return &janus_streaming_plugin;
}

/* Parameter validation */
static struct janus_json_parameter request_parameters[] = {
	{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter id_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter watch_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"pin", JSON_STRING, 0},
	{"offer_audio", JANUS_JSON_BOOL, 0},
	{"offer_video", JANUS_JSON_BOOL, 0},
	{"offer_data", JANUS_JSON_BOOL, 0},
	{"restart", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter adminkey_parameters[] = {
	{"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter edit_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"new_description", JSON_STRING, 0},
	{"new_secret", JSON_STRING, 0},
	{"new_pin", JSON_STRING, 0},
	{"new_is_private", JANUS_JSON_BOOL, 0},
	{"permanent", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter create_parameters[] = {
	{"type", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"secret", JSON_STRING, 0},
	{"pin", JSON_STRING, 0},
	{"permanent", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter rtp_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"name", JSON_STRING, 0},
	{"description", JSON_STRING, 0},
	{"is_private", JANUS_JSON_BOOL, 0},
	{"audio", JANUS_JSON_BOOL, 0},
	{"video", JANUS_JSON_BOOL, 0},
	{"data", JANUS_JSON_BOOL, 0},
	{"collision", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"threads", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"srtpsuite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"srtpcrypto", JSON_STRING, 0}
};
static struct janus_json_parameter live_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"name", JSON_STRING, 0},
	{"description", JSON_STRING, 0},
	{"is_private", JANUS_JSON_BOOL, 0},
	{"filename", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"audio", JANUS_JSON_BOOL, 0},
	{"video", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter ondemand_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"name", JSON_STRING, 0},
	{"description", JSON_STRING, 0},
	{"is_private", JANUS_JSON_BOOL, 0},
	{"filename", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"audio", JANUS_JSON_BOOL, 0},
	{"video", JANUS_JSON_BOOL, 0}
};
#ifdef HAVE_LIBCURL
static struct janus_json_parameter rtsp_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"name", JSON_STRING, 0},
	{"description", JSON_STRING, 0},
	{"is_private", JANUS_JSON_BOOL, 0},
	{"url", JSON_STRING, 0},
	{"rtsp_user", JSON_STRING, 0},
	{"rtsp_pwd", JSON_STRING, 0},
	{"audio", JANUS_JSON_BOOL, 0},
	{"audiortpmap", JSON_STRING, 0},
	{"audiofmtp", JSON_STRING, 0},
	{"audiopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"video", JANUS_JSON_BOOL, 0},
	{"videortpmap", JSON_STRING, 0},
	{"videofmtp", JSON_STRING, 0},
	{"videopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"rtspiface", JSON_STRING, 0},
	{"rtsp_failcheck", JANUS_JSON_BOOL, 0}
};
#endif
static struct janus_json_parameter rtp_audio_parameters[] = {
	{"audiomcast", JSON_STRING, 0},
	{"audioport", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"audiortcpport", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"audiopt", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"audiortpmap", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"audiofmtp", JSON_STRING, 0},
	{"audioiface", JSON_STRING, 0},
	{"audioskew", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter rtp_video_parameters[] = {
	{"videomcast", JSON_STRING, 0},
	{"videoport", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"videortcpport", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"videopt", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"videortpmap", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"videofmtp", JSON_STRING, 0},
	{"videobufferkf", JANUS_JSON_BOOL, 0},
	{"videoiface", JSON_STRING, 0},
	{"videosimulcast", JANUS_JSON_BOOL, 0},
	{"videoport2", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"videoport3", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"videoskew", JANUS_JSON_BOOL, 0},
	{"videosvc", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter rtp_data_parameters[] = {
	{"dataport", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"databuffermsg", JANUS_JSON_BOOL, 0},
	{"dataiface", JSON_STRING, 0}
};
static struct janus_json_parameter destroy_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"permanent", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter recording_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter recording_start_parameters[] = {
	{"audio", JSON_STRING, 0},
	{"video", JSON_STRING, 0},
	{"data", JSON_STRING, 0}
};
static struct janus_json_parameter recording_stop_parameters[] = {
	{"audio", JANUS_JSON_BOOL, 0},
	{"video", JANUS_JSON_BOOL, 0},
	{"data", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter simulcast_parameters[] = {
	{"substream", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"temporal", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter svc_parameters[] = {
	{"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter configure_parameters[] = {
	{"audio", JANUS_JSON_BOOL, 0},
	{"video", JANUS_JSON_BOOL, 0},
	{"data", JANUS_JSON_BOOL, 0},
	/* For VP8 (or H.264) simulcast */
	{"substream", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"temporal", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	/* For VP9 SVC */
	{"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};

/* Static configuration instance */
static janus_config *config = NULL;
static const char *config_folder = NULL;
static janus_mutex config_mutex = JANUS_MUTEX_INITIALIZER;

/* Useful stuff */
static volatile gint initialized = 0, stopping = 0;
static gboolean notify_events = TRUE;
static janus_callbacks *gateway = NULL;
static GThread *handler_thread;
static void *janus_streaming_handler(void *data);

/* RTP range to use for random ports */
#define DEFAULT_RTP_RANGE_MIN 10000
#define DEFAULT_RTP_RANGE_MAX 60000
static uint16_t rtp_range_min = DEFAULT_RTP_RANGE_MIN;
static uint16_t rtp_range_max = DEFAULT_RTP_RANGE_MAX;
static uint16_t rtp_range_slider = DEFAULT_RTP_RANGE_MIN;

static void *janus_streaming_ondemand_thread(void *data);
static void *janus_streaming_filesource_thread(void *data);
static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data);
static void janus_streaming_relay_rtcp_packet(gpointer data, gpointer user_data);
static void *janus_streaming_relay_thread(void *data);
static void janus_streaming_hangup_media_internal(janus_plugin_session *handle);

typedef enum janus_streaming_type {
	janus_streaming_type_none = 0,
	janus_streaming_type_live,
	janus_streaming_type_on_demand,
} janus_streaming_type;

typedef enum janus_streaming_source {
	janus_streaming_source_none = 0,
	janus_streaming_source_file,
	janus_streaming_source_rtp,
} janus_streaming_source;

typedef struct janus_streaming_rtp_keyframe {
	gboolean enabled;
	/* If enabled, we store the packets of the last keyframe, to immediately send them for new viewers */
	GList *latest_keyframe;
	/* This is where we store packets while we're still collecting the whole keyframe */
	GList *temp_keyframe;
	guint32 temp_ts;
	janus_mutex mutex;
} janus_streaming_rtp_keyframe;

typedef struct janus_streaming_rtp_relay_packet {
	janus_rtp_header *data;
	gint length;
	gboolean is_rtp;	/* This may be a data packet and not RTP */
	gboolean is_video;
	gboolean is_keyframe;
	gboolean simulcast;
	uint32_t ssrc[3];
	janus_videocodec codec;
	int substream;
	uint32_t timestamp;
	uint16_t seq_number;
	/* The following are only relevant for VP9 SVC*/
	gboolean svc;
	int spatial_layer;
	int temporal_layer;
	uint8_t pbit, dbit, ubit, bbit, ebit;
} janus_streaming_rtp_relay_packet;
static janus_streaming_rtp_relay_packet exit_packet;
static void janus_streaming_rtp_relay_packet_free(janus_streaming_rtp_relay_packet *pkt) {
	if(pkt == NULL || pkt == &exit_packet)
		return;
	g_free(pkt->data);
	g_free(pkt);

}

#ifdef HAVE_LIBCURL
typedef struct janus_streaming_buffer {
	char *buffer;
	size_t size;
} janus_streaming_buffer;
#endif

typedef struct janus_streaming_rtp_source {
	gint audio_port, remote_audio_port;
	gint audio_rtcp_port, remote_audio_rtcp_port;
	in_addr_t audio_mcast;
	gint video_port[3], remote_video_port;
	gint video_rtcp_port, remote_video_rtcp_port;
	in_addr_t video_mcast;
	gint data_port;
	janus_recorder *arc;	/* The Janus recorder instance for this streams's audio, if enabled */
	janus_recorder *vrc;	/* The Janus recorder instance for this streams's video, if enabled */
	janus_recorder *drc;	/* The Janus recorder instance for this streams's data, if enabled */
	janus_mutex rec_mutex;	/* Mutex to protect the recorders from race conditions */
	janus_rtp_switching_context context[3];
	int audio_fd;
	int video_fd[3];
	int data_fd;
	int pipefd[2];			/* Just needed to quickly interrupt the poll when it's time to wrap up */
	int audio_rtcp_fd;
	int video_rtcp_fd;
	gboolean simulcast;
	gboolean svc;
	gboolean askew, vskew;
	gint64 last_received_audio;
	gint64 last_received_video;
	gint64 last_received_data;
	uint32_t audio_ssrc;		/* Only needed for fixing outgoing RTCP packets */
	uint32_t video_ssrc;		/* Only needed for fixing outgoing RTCP packets */
	volatile gint need_pli;		/* Whether we need to send a PLI later */
	volatile gint sending_pli;	/* Whether we're currently sending a PLI */
	gint64 pli_latest;			/* Time of latest sent PLI (to avoid flooding) */
	uint32_t lowest_bitrate;	/* Lowest bitrate received by viewers via REMB since last update */
	gint64 remb_latest;			/* Time of latest sent REMB (to avoid flooding) */
	struct sockaddr audio_rtcp_addr, video_rtcp_addr;
#ifdef HAVE_LIBCURL
	gboolean rtsp;
	CURL *curl;
	janus_streaming_buffer *curldata;
	char *rtsp_url;
	char *rtsp_username, *rtsp_password;
	int ka_timeout;
	char *rtsp_ahost, *rtsp_vhost;
	gboolean reconnecting;
	gint64 reconnect_timer;
	janus_mutex rtsp_mutex;
#endif
	janus_streaming_rtp_keyframe keyframe;
	gboolean buffermsg;
	int rtp_collision;
	void *last_msg;
	janus_mutex buffermsg_mutex;
	janus_network_address audio_iface;
	janus_network_address video_iface;
	janus_network_address data_iface;
	/* Only needed for SRTP forwarders */
	gboolean is_srtp;
	int srtpsuite;
	char *srtpcrypto;
	srtp_t srtp_ctx;
	srtp_policy_t srtp_policy;
} janus_streaming_rtp_source;

typedef struct janus_streaming_file_source {
	char *filename;
} janus_streaming_file_source;

/* used for audio/video fd and RTCP fd */
typedef struct multiple_fds {
	int fd;
	int rtcp_fd;
} multiple_fds;

typedef struct janus_streaming_codecs {
	gint audio_pt;
	char *audio_rtpmap;
	char *audio_fmtp;
	janus_videocodec video_codec;
	gint video_pt;
	char *video_rtpmap;
	char *video_fmtp;
} janus_streaming_codecs;

typedef struct janus_streaming_mountpoint {
	guint64 id;
	char *name;
	char *description;
	gboolean is_private;
	char *secret;
	char *pin;
	gboolean enabled;
	gboolean active;
	GThread *thread;	/* A mountpoint may or may not have a thread */
	janus_streaming_type streaming_type;
	janus_streaming_source streaming_source;
	void *source;	/* Can differ according to the source type */
	GDestroyNotify source_destroy;
	janus_streaming_codecs codecs;
	gboolean audio, video, data;
	GList *viewers;
	int helper_threads;		/* Only relevant for RTP mountpoints */
	GList *threads;			/* Only relevant for RTP mountpoints */
	volatile gint destroyed;
	janus_mutex mutex;
	janus_refcount ref;
} janus_streaming_mountpoint;
GHashTable *mountpoints;
janus_mutex mountpoints_mutex;
static char *admin_key = NULL;

typedef struct janus_streaming_helper {
	janus_streaming_mountpoint *mp;
	guint id;
	GThread *thread;
	int num_viewers;
	GList *viewers;
	GAsyncQueue *queued_packets;
	volatile gint destroyed;
	janus_mutex mutex;
} janus_streaming_helper;
static void *janus_streaming_helper_thread(void *data);
static void janus_streaming_helper_rtprtcp_packet(gpointer data, gpointer user_data);

/* Helper to create an RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
janus_streaming_mountpoint *janus_streaming_create_rtp_source(
		uint64_t id, char *name, char *desc,
		int srtpsuite, char *srtpcrypto, int threads,
		gboolean doaudio, char *amcast, const janus_network_address *aiface, uint16_t aport, uint16_t artcpport, uint8_t acodec, char *artpmap, char *afmtp, gboolean doaskew,
		gboolean dovideo, char *vmcast, const janus_network_address *viface, uint16_t vport, uint16_t vrtcpport, uint8_t vcodec, char *vrtpmap, char *vfmtp, gboolean bufferkf,
			gboolean simulcast, uint16_t vport2, uint16_t vport3, gboolean svc, gboolean dovskew, int rtp_collision,
		gboolean dodata, const janus_network_address *diface, uint16_t dport, gboolean buffermsg);
/* Helper to create a file/ondemand live source */
janus_streaming_mountpoint *janus_streaming_create_file_source(
		uint64_t id, char *name, char *desc, char *filename,
		gboolean live, gboolean doaudio, gboolean dovideo);
/* Helper to create a rtsp live source */
janus_streaming_mountpoint *janus_streaming_create_rtsp_source(
		uint64_t id, char *name, char *desc,
		char *url, char *username, char *password,
		gboolean doaudio, int audiopt, char *artpmap, char *afmtp,
		gboolean dovideo, int videopt, char *vrtpmap, char *vfmtp,
		const janus_network_address *iface,
		gboolean error_on_failure);


typedef struct janus_streaming_message {
	janus_plugin_session *handle;
	char *transaction;
	json_t *message;
	json_t *jsep;
} janus_streaming_message;
static GAsyncQueue *messages = NULL;
static janus_streaming_message exit_message;

typedef struct janus_streaming_session {
	janus_plugin_session *handle;
	janus_streaming_mountpoint *mountpoint;
	gint64 sdp_sessid;
	gint64 sdp_version;
	gboolean started;
	gboolean paused;
	gboolean audio, video, data;		/* Whether audio, video and/or data must be sent to this listener */
	janus_rtp_switching_context context;
	janus_rtp_simulcasting_context sim_context;
	janus_vp8_simulcast_context vp8_context;
	/* The following are only relevant the mountpoint is VP9-SVC, and are not to be confused with VP8
	 * simulcast, which has similar info (substream/templayer) but in a completely different context */
	int spatial_layer, target_spatial_layer;
	int temporal_layer, target_temporal_layer;
	gboolean stopping;
	volatile gint hangingup;
	volatile gint destroyed;
	janus_refcount ref;
} janus_streaming_session;
static GHashTable *sessions;
static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER;

static void janus_streaming_session_destroy(janus_streaming_session *session) {
	if(session && g_atomic_int_compare_and_exchange(&session->destroyed, 0, 1))
		janus_refcount_decrease(&session->ref);
}

static void janus_streaming_session_free(const janus_refcount *session_ref) {
	janus_streaming_session *session = janus_refcount_containerof(session_ref, janus_streaming_session, ref);
	/* Remove the reference to the core plugin session */
	janus_refcount_decrease(&session->handle->ref);
	/* This session can be destroyed, free all the resources */
	g_free(session);
}

static void janus_streaming_mountpoint_destroy(janus_streaming_mountpoint *mountpoint) {
	if(!mountpoint)
		return;
	if(!g_atomic_int_compare_and_exchange(&mountpoint->destroyed, 0, 1))
		return;
	/* If this is an RTP source, interrupt the poll */
	if(mountpoint->streaming_source == janus_streaming_source_rtp) {
		janus_streaming_rtp_source *source = mountpoint->source;
		if(source != NULL && source->pipefd[1] > 0) {
			int code = 1;
			ssize_t res = 0;
			do {
				res = write(source->pipefd[1], &code, sizeof(int));
			} while(res == -1 && errno == EINTR);
		}
	}
	/* Wait for the thread to finish */
	if(mountpoint->thread != NULL)
		g_thread_join(mountpoint->thread);
	/* Get rid of the helper threads, if any */
	if(mountpoint->helper_threads > 0) {
		GList *l = mountpoint->threads;
		while(l) {
			janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
			g_async_queue_push(ht->queued_packets, &exit_packet);
			l = l->next;
		}
	}
	/* Decrease the counter */
	janus_refcount_decrease(&mountpoint->ref);
}

static void janus_streaming_mountpoint_free(const janus_refcount *mp_ref) {
	janus_streaming_mountpoint *mp = janus_refcount_containerof(mp_ref, janus_streaming_mountpoint, ref);
	/* This mountpoint can be destroyed, free all the resources */

	g_free(mp->name);
	g_free(mp->description);
	g_free(mp->secret);
	g_free(mp->pin);
	janus_mutex_lock(&mp->mutex);
	if(mp->viewers != NULL)
		g_list_free(mp->viewers);
	janus_mutex_unlock(&mp->mutex);

	if(mp->source != NULL && mp->source_destroy != NULL) {
		mp->source_destroy(mp->source);
	}

	g_free(mp->codecs.audio_rtpmap);
	g_free(mp->codecs.audio_fmtp);
	g_free(mp->codecs.video_rtpmap);
	g_free(mp->codecs.video_fmtp);

	g_free(mp);
}

static void janus_streaming_message_free(janus_streaming_message *msg) {
	if(!msg || msg == &exit_message)
		return;

	if(msg->handle && msg->handle->plugin_handle) {
		janus_streaming_session *session = (janus_streaming_session *)msg->handle->plugin_handle;
		janus_refcount_decrease(&session->ref);
	}
	msg->handle = NULL;

	g_free(msg->transaction);
	msg->transaction = NULL;
	if(msg->message)
		json_decref(msg->message);
	msg->message = NULL;
	if(msg->jsep)
		json_decref(msg->jsep);
	msg->jsep = NULL;

	g_free(msg);
}


/* Helper method to send an RTCP PLI */
static void janus_streaming_rtcp_pli_send(janus_streaming_rtp_source *source) {
	if(source == NULL || source->video_rtcp_fd < 0 || source->video_rtcp_addr.sa_family == 0)
		return;
	if(!g_atomic_int_compare_and_exchange(&source->sending_pli, 0, 1))
		return;
	gint64 now = janus_get_monotonic_time();
	if(now - source->pli_latest < G_USEC_PER_SEC) {
		/* We just sent a PLI less than a second ago, schedule a new delivery later */
		g_atomic_int_set(&source->need_pli, 1);
		g_atomic_int_set(&source->sending_pli, 0);
		return;
	}
	/* Update the time of when we last sent a keyframe request */
	g_atomic_int_set(&source->need_pli, 0);
	source->pli_latest = janus_get_monotonic_time();
	JANUS_LOG(LOG_HUGE, "Sending PLI\n");
	/* Generate a PLI */
	char rtcp_buf[12];
	int rtcp_len = 12;
	janus_rtcp_pli((char *)&rtcp_buf, rtcp_len);
	janus_rtcp_fix_ssrc(NULL, rtcp_buf, rtcp_len, 1, 1, source->video_ssrc);
	/* Send the packet */
	int sent = 0;
	if((sent = sendto(source->video_rtcp_fd, rtcp_buf, rtcp_len, 0,
			&source->video_rtcp_addr, sizeof(source->video_rtcp_addr))) < 0) {
		JANUS_LOG(LOG_ERR, "Error in sendto... %d (%s)\n", errno, strerror(errno));
	} else {
		JANUS_LOG(LOG_HUGE, "Sent %d/%d bytes\n", sent, rtcp_len);
	}
	g_atomic_int_set(&source->sending_pli, 0);
}

/* Helper method to send an RTCP REMB */
static void janus_streaming_rtcp_remb_send(janus_streaming_rtp_source *source) {
	if(source == NULL || source->video_rtcp_fd < 0 || source->video_rtcp_addr.sa_family == 0)
		return;
	/* Update the time of when we last sent REMB feedback */
	source->remb_latest = janus_get_monotonic_time();
	/* Generate a REMB */
	char rtcp_buf[24];
	int rtcp_len = 24;
	janus_rtcp_remb((char *)(&rtcp_buf), rtcp_len, source->lowest_bitrate);
	janus_rtcp_fix_ssrc(NULL, rtcp_buf, rtcp_len, 1, 1, source->video_ssrc);
	JANUS_LOG(LOG_HUGE, "Sending REMB: %"SCNu32"\n", source->lowest_bitrate);
	/* Reset the lowest bitrate */
	source->lowest_bitrate = 0;
	/* Send the packet */
	int sent = 0;
	if((sent = sendto(source->video_rtcp_fd, rtcp_buf, rtcp_len, 0,
			&source->video_rtcp_addr, sizeof(source->video_rtcp_addr))) < 0) {
		JANUS_LOG(LOG_ERR, "Error in sendto... %d (%s)\n", errno, strerror(errno));
	} else {
		JANUS_LOG(LOG_HUGE, "Sent %d/%d bytes\n", sent, rtcp_len);
	}
}


/* Error codes */
#define JANUS_STREAMING_ERROR_NO_MESSAGE			450
#define JANUS_STREAMING_ERROR_INVALID_JSON			451
#define JANUS_STREAMING_ERROR_INVALID_REQUEST		452
#define JANUS_STREAMING_ERROR_MISSING_ELEMENT		453
#define JANUS_STREAMING_ERROR_INVALID_ELEMENT		454
#define JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT	455
#define JANUS_STREAMING_ERROR_CANT_CREATE			456
#define JANUS_STREAMING_ERROR_UNAUTHORIZED			457
#define JANUS_STREAMING_ERROR_CANT_SWITCH			458
#define JANUS_STREAMING_ERROR_CANT_RECORD			459
#define JANUS_STREAMING_ERROR_UNKNOWN_ERROR			470


/* Plugin implementation */
int janus_streaming_init(janus_callbacks *callback, const char *config_path) {
#ifdef HAVE_LIBCURL
	curl_global_init(CURL_GLOBAL_ALL);
#endif
	if(g_atomic_int_get(&stopping)) {
		/* Still stopping from before */
		return -1;
	}
	if(callback == NULL || config_path == NULL) {
		/* Invalid arguments */
		return -1;
	}

	struct ifaddrs *ifas = NULL;
	if(getifaddrs(&ifas) == -1) {
		JANUS_LOG(LOG_ERR, "Unable to acquire list of network devices/interfaces; some configurations may not work as expected...\n");
	}

	/* Read configuration */
	char filename[255];
	g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_STREAMING_PACKAGE);
	JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
	config = janus_config_parse(filename);
	if(config == NULL) {
		JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_STREAMING_PACKAGE);
		g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_STREAMING_PACKAGE);
		JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
		config = janus_config_parse(filename);
	}
	config_folder = config_path;
	if(config != NULL)
		janus_config_print(config);

	mountpoints = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, (GDestroyNotify)janus_streaming_mountpoint_destroy);

	/* Threads will expect this to be set */
	g_atomic_int_set(&initialized, 1);

	/* Parse configuration to populate the mountpoints */
	if(config != NULL) {
		janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general");
		/* Any admin key to limit who can "create"? */
		janus_config_item *key = janus_config_get(config, config_general, janus_config_type_item, "admin_key");
		if(key != NULL && key->value != NULL)
			admin_key = g_strdup(key->value);
		janus_config_item *range = janus_config_get(config, config_general, janus_config_type_item, "rtp_port_range");
		if(range && range->value) {
			/* Split in min and max port */
			char *maxport = strrchr(range->value, '-');
			if(maxport != NULL) {
				*maxport = '\0';
				maxport++;
				rtp_range_min = atoi(range->value);
				rtp_range_max = atoi(maxport);
				maxport--;
				*maxport = '-';
			}
			if(rtp_range_min > rtp_range_max) {
				uint16_t temp_port = rtp_range_min;
				rtp_range_min = rtp_range_max;
				rtp_range_max = temp_port;
			}
			if(rtp_range_min % 2)
				rtp_range_min++;	/* Pick an even port for RTP */
			if(rtp_range_min > rtp_range_max) {
				JANUS_LOG(LOG_WARN, "Incorrect port range (%u -- %u), switching min and max\n", rtp_range_min, rtp_range_max);
				uint16_t range_temp = rtp_range_max;
				rtp_range_max = rtp_range_min;
				rtp_range_min = range_temp;
			}
			if(rtp_range_max == 0)
				rtp_range_max = 65535;
			rtp_range_slider = rtp_range_min;
			JANUS_LOG(LOG_VERB, "Streaming RTP/RTCP port range: %u -- %u\n", rtp_range_min, rtp_range_max);
		}
		janus_config_item *events = janus_config_get(config, config_general, janus_config_type_item, "events");
		if(events != NULL && events->value != NULL)
			notify_events = janus_is_true(events->value);
		if(!notify_events && callback->events_is_enabled()) {
			JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_STREAMING_NAME);
		}
		/* Iterate on all mountpoints */
		GList *clist = janus_config_get_categories(config, NULL), *cl = clist;
		while(cl != NULL) {
			janus_config_category *cat = (janus_config_category *)cl->data;
			if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
				cl = cl->next;
				continue;
			}
			JANUS_LOG(LOG_VERB, "Adding stream '%s'\n", cat->name);
			janus_config_item *type = janus_config_get(config, cat, janus_config_type_item, "type");
			if(type == NULL || type->value == NULL) {
				JANUS_LOG(LOG_WARN, "  -- Invalid type, skipping stream '%s'...\n", cat->name);
				cl = cl->next;
				continue;
			}
			if(!strcasecmp(type->value, "rtp")) {
				janus_network_address video_iface, audio_iface, data_iface;
				/* RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
				janus_config_item *id = janus_config_get(config, cat, janus_config_type_item, "id");
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *priv = janus_config_get(config, cat, janus_config_type_item, "is_private");
				janus_config_item *secret = janus_config_get(config, cat, janus_config_type_item, "secret");
				janus_config_item *pin = janus_config_get(config, cat, janus_config_type_item, "pin");
				janus_config_item *audio = janus_config_get(config, cat, janus_config_type_item, "audio");
				janus_config_item *askew = janus_config_get(config, cat, janus_config_type_item, "audioskew");
				janus_config_item *video = janus_config_get(config, cat, janus_config_type_item, "video");
				janus_config_item *vskew = janus_config_get(config, cat, janus_config_type_item, "videoskew");
				janus_config_item *vsvc = janus_config_get(config, cat, janus_config_type_item, "videosvc");
				janus_config_item *data = janus_config_get(config, cat, janus_config_type_item, "data");
				janus_config_item *diface = janus_config_get(config, cat, janus_config_type_item, "dataiface");
				janus_config_item *amcast = janus_config_get(config, cat, janus_config_type_item, "audiomcast");
				janus_config_item *aiface = janus_config_get(config, cat, janus_config_type_item, "audioiface");
				janus_config_item *aport = janus_config_get(config, cat, janus_config_type_item, "audioport");
				janus_config_item *artcpport = janus_config_get(config, cat, janus_config_type_item, "audiortcpport");
				janus_config_item *acodec = janus_config_get(config, cat, janus_config_type_item, "audiopt");
				janus_config_item *artpmap = janus_config_get(config, cat, janus_config_type_item, "audiortpmap");
				janus_config_item *afmtp = janus_config_get(config, cat, janus_config_type_item, "audiofmtp");
				janus_config_item *vmcast = janus_config_get(config, cat, janus_config_type_item, "videomcast");
				janus_config_item *viface = janus_config_get(config, cat, janus_config_type_item, "videoiface");
				janus_config_item *vport = janus_config_get(config, cat, janus_config_type_item, "videoport");
				janus_config_item *vrtcpport = janus_config_get(config, cat, janus_config_type_item, "videortcpport");
				janus_config_item *vcodec = janus_config_get(config, cat, janus_config_type_item, "videopt");
				janus_config_item *vrtpmap = janus_config_get(config, cat, janus_config_type_item, "videortpmap");
				janus_config_item *vfmtp = janus_config_get(config, cat, janus_config_type_item, "videofmtp");
				janus_config_item *vkf = janus_config_get(config, cat, janus_config_type_item, "videobufferkf");
				janus_config_item *vsc = janus_config_get(config, cat, janus_config_type_item, "videosimulcast");
				janus_config_item *vport2 = janus_config_get(config, cat, janus_config_type_item, "videoport2");
				janus_config_item *vport3 = janus_config_get(config, cat, janus_config_type_item, "videoport3");
				janus_config_item *dport = janus_config_get(config, cat, janus_config_type_item, "dataport");
				janus_config_item *dbm = janus_config_get(config, cat, janus_config_type_item, "databuffermsg");
				janus_config_item *rtpcollision = janus_config_get(config, cat, janus_config_type_item, "collision");
				janus_config_item *threads = janus_config_get(config, cat, janus_config_type_item, "threads");
				janus_config_item *ssuite = janus_config_get(config, cat, janus_config_type_item, "srtpsuite");
				janus_config_item *scrypto = janus_config_get(config, cat, janus_config_type_item, "srtpcrypto");
				gboolean is_private = priv && priv->value && janus_is_true(priv->value);
				gboolean doaudio = audio && audio->value && janus_is_true(audio->value);
				gboolean doaskew = audio && askew && askew->value && janus_is_true(askew->value);
				gboolean dovideo = video && video->value && janus_is_true(video->value);
				gboolean dovskew = video && vskew && vskew->value && janus_is_true(vskew->value);
				gboolean dosvc = video && vsvc && vsvc->value && janus_is_true(vsvc->value);
				gboolean dodata = data && data->value && janus_is_true(data->value);
				gboolean bufferkf = video && vkf && vkf->value && janus_is_true(vkf->value);
				gboolean simulcast = video && vsc && vsc->value && janus_is_true(vsc->value);
				if(simulcast && bufferkf) {
					/* FIXME We'll need to take care of this */
					JANUS_LOG(LOG_WARN, "Simulcasting enabled, so disabling buffering of keyframes\n");
					bufferkf = FALSE;
				}
				gboolean buffermsg = data && dbm && dbm->value && janus_is_true(dbm->value);
				if(!doaudio && !dovideo && !dodata) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', no audio, video or data have to be streamed...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(doaudio &&
						(aport == NULL || aport->value == NULL || atoi(aport->value) == 0 ||
						acodec == NULL || acodec->value == NULL ||
						artpmap == NULL || artpmap->value == NULL)) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', missing mandatory information for audio...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(doaudio && aiface) {
					if(!ifas) {
						JANUS_LOG(LOG_ERR, "Skipping 'rtp' stream '%s', it relies on network configuration but network device information is unavailable...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(janus_network_lookup_interface(ifas, aiface->value, &audio_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for audio...\n", cat->name);
						cl = cl->next;
						continue;
					}
				}
				if(dovideo &&
						(vport == NULL || vport->value == NULL || atoi(vport->value) == 0 ||
						vcodec == NULL || vcodec->value == NULL ||
						vrtpmap == NULL || vrtpmap->value == NULL)) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', missing mandatory information for video...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(dodata && (dport == NULL || dport->value == NULL || atoi(dport->value) == 0)) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', missing mandatory information for data...\n", cat->name);
					cl = cl->next;
					continue;
				}
#ifndef HAVE_SCTP
				if(dodata) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s': no datachannels support......\n", cat->name);
					cl = cl->next;
					continue;
				}
#endif
				if(dodata && diface) {
					if(!ifas) {
						JANUS_LOG(LOG_ERR, "Skipping 'rtp' stream '%s', it relies on network configuration but network device information is unavailable...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(janus_network_lookup_interface(ifas, diface->value, &data_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for data...\n", cat->name);
						cl = cl->next;
						continue;
					}
				}
				if(dovideo && viface) {
					if(!ifas) {
						JANUS_LOG(LOG_ERR, "Skipping 'rtp' stream '%s', it relies on network configuration but network device information is unavailable...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(janus_network_lookup_interface(ifas, viface->value, &video_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for video...\n", cat->name);
						cl = cl->next;
						continue;
					}
				}
				if(ssuite && ssuite->value && atoi(ssuite->value) != 32 && atoi(ssuite->value) != 80) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid SRTP suite...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(rtpcollision && rtpcollision->value && atoi(rtpcollision->value) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid collision configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(threads && threads->value && atoi(threads->value) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid threads configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(id == NULL || id->value == NULL) {
					JANUS_LOG(LOG_VERB, "Missing id for stream '%s', will generate a random one...\n", cat->name);
				} else {
					janus_mutex_lock(&mountpoints_mutex);
					guint64 mpid = g_ascii_strtoull(id->value, 0, 10);
					janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					if(mp != NULL) {
						JANUS_LOG(LOG_ERR, "A stream with the provided ID %s already exists, skipping '%s'\n", id->value, cat->name);
						cl = cl->next;
						continue;
					}
				}
				JANUS_LOG(LOG_VERB, "Audio %s, Video %s, Data %s\n",
					doaudio ? "enabled" : "NOT enabled",
					dovideo ? "enabled" : "NOT enabled",
					dodata ? "enabled" : "NOT enabled");
				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_rtp_source(
						(id && id->value) ? g_ascii_strtoull(id->value, 0, 10) : 0,
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						ssuite && ssuite->value ? atoi(ssuite->value) : 0,
						scrypto && scrypto->value ? (char *)scrypto->value : NULL,
						(threads && threads->value) ?  atoi(threads->value) : 0,
						doaudio,
						amcast ? (char *)amcast->value : NULL,
						doaudio && aiface && aiface->value ? &audio_iface : NULL,
						(aport && aport->value) ? atoi(aport->value) : 0,
						(artcpport && artcpport->value) ? atoi(artcpport->value) : 0,
						(acodec && acodec->value) ? atoi(acodec->value) : 0,
						artpmap ? (char *)artpmap->value : NULL,
						afmtp ? (char *)afmtp->value : NULL,
						doaskew,
						dovideo,
						vmcast ? (char *)vmcast->value : NULL,
						dovideo && viface && viface->value ? &video_iface : NULL,
						(vport && vport->value) ? atoi(vport->value) : 0,
						(vrtcpport && vrtcpport->value) ? atoi(vrtcpport->value) : 0,
						(vcodec && vcodec->value) ? atoi(vcodec->value) : 0,
						vrtpmap ? (char *)vrtpmap->value : NULL,
						vfmtp ? (char *)vfmtp->value : NULL,
						bufferkf,
						simulcast,
						(vport2 && vport2->value) ? atoi(vport2->value) : 0,
						(vport3 && vport3->value) ? atoi(vport3->value) : 0,
						dosvc,
						dovskew,
						(rtpcollision && rtpcollision->value) ?  atoi(rtpcollision->value) : 0,
						dodata,
						dodata && diface && diface->value ? &data_iface : NULL,
						(dport && dport->value) ? atoi(dport->value) : 0,
						buffermsg)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'rtp' stream '%s'...\n", cat->name);
					cl = cl->next;
					continue;
				}
				mp->is_private = is_private;
				if(secret && secret->value)
					mp->secret = g_strdup(secret->value);
				if(pin && pin->value)
					mp->pin = g_strdup(pin->value);
			} else if(!strcasecmp(type->value, "live")) {
				/* File live source */
				janus_config_item *id = janus_config_get(config, cat, janus_config_type_item, "id");
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *priv = janus_config_get(config, cat, janus_config_type_item, "is_private");
				janus_config_item *secret = janus_config_get(config, cat, janus_config_type_item, "secret");
				janus_config_item *pin = janus_config_get(config, cat, janus_config_type_item, "pin");
				janus_config_item *file = janus_config_get(config, cat, janus_config_type_item, "filename");
				janus_config_item *audio = janus_config_get(config, cat, janus_config_type_item, "audio");
				janus_config_item *video = janus_config_get(config, cat, janus_config_type_item, "video");
				if(file == NULL || file->value == NULL) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' stream '%s', missing mandatory information...\n", cat->name);
					cl = cl->next;
					continue;
				}
				gboolean is_private = priv && priv->value && janus_is_true(priv->value);
				gboolean doaudio = audio && audio->value && janus_is_true(audio->value);
				gboolean dovideo = video && video->value && janus_is_true(video->value);
				/* TODO We should support something more than raw a-Law and mu-Law streams... */
				if(!doaudio || dovideo) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' stream '%s', we only support audio file streaming right now...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(!strstr(file->value, ".alaw") && !strstr(file->value, ".mulaw")) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' stream '%s', unsupported format (we only support raw mu-Law and a-Law files right now)\n", cat->name);
					cl = cl->next;
					continue;
				}
				FILE *audiofile = fopen(file->value, "rb");
				if(!audiofile) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' stream, no such file '%s'...\n", file->value);
					cl = cl->next;
					continue;
				}
				fclose(audiofile);
				if(id == NULL || id->value == NULL) {
					JANUS_LOG(LOG_VERB, "Missing id for stream '%s', will generate a random one...\n", cat->name);
				} else {
					janus_mutex_lock(&mountpoints_mutex);
					guint64 mpid = g_ascii_strtoull(id->value, 0, 10);
					janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					if(mp != NULL) {
						JANUS_LOG(LOG_ERR, "A stream with the provided ID %s already exists, skipping '%s'\n", id->value, cat->name);
						cl = cl->next;
						continue;
					}
				}
				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_file_source(
						(id && id->value) ? g_ascii_strtoull(id->value, 0, 10) : 0,
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						(char *)file->value,
						TRUE, doaudio, dovideo)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'live' stream '%s'...\n", cat->name);
					cl = cl->next;
					continue;
				}
				mp->is_private = is_private;
				if(secret && secret->value)
					mp->secret = g_strdup(secret->value);
				if(pin && pin->value)
					mp->pin = g_strdup(pin->value);
			} else if(!strcasecmp(type->value, "ondemand")) {
				/* mu-Law file on demand source */
				janus_config_item *id = janus_config_get(config, cat, janus_config_type_item, "id");
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *priv = janus_config_get(config, cat, janus_config_type_item, "is_private");
				janus_config_item *secret = janus_config_get(config, cat, janus_config_type_item, "secret");
				janus_config_item *pin = janus_config_get(config, cat, janus_config_type_item, "pin");
				janus_config_item *file = janus_config_get(config, cat, janus_config_type_item, "filename");
				janus_config_item *audio = janus_config_get(config, cat, janus_config_type_item, "audio");
				janus_config_item *video = janus_config_get(config, cat, janus_config_type_item, "video");
				if(file == NULL || file->value == NULL) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream '%s', missing mandatory information...\n", cat->name);
					cl = cl->next;
					continue;
				}
				gboolean is_private = priv && priv->value && janus_is_true(priv->value);
				gboolean doaudio = audio && audio->value && janus_is_true(audio->value);
				gboolean dovideo = video && video->value && janus_is_true(video->value);
				/* TODO We should support something more than raw a-Law and mu-Law streams... */
				if(!doaudio || dovideo) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream '%s', we only support audio file streaming right now...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(!strstr(file->value, ".alaw") && !strstr(file->value, ".mulaw")) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream '%s', unsupported format (we only support raw mu-Law and a-Law files right now)\n", cat->name);
					cl = cl->next;
					continue;
				}
				FILE *audiofile = fopen(file->value, "rb");
				if(!audiofile) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, no such file '%s'...\n", file->value);
					cl = cl->next;
					continue;
				}
				fclose(audiofile);
				if(id == NULL || id->value == NULL) {
					JANUS_LOG(LOG_VERB, "Missing id for stream '%s', will generate a random one...\n", cat->name);
				} else {
					janus_mutex_lock(&mountpoints_mutex);
					guint64 mpid = g_ascii_strtoull(id->value, 0, 10);
					janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					if(mp != NULL) {
						JANUS_LOG(LOG_ERR, "A stream with the provided ID %s already exists, skipping '%s'\n", id->value, cat->name);
						cl = cl->next;
						continue;
					}
				}
				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_file_source(
						(id && id->value) ? g_ascii_strtoull(id->value, 0, 10) : 0,
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						(char *)file->value,
						FALSE, doaudio, dovideo)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'ondemand' stream '%s'...\n", cat->name);
					cl = cl->next;
					continue;
				}
				mp->is_private = is_private;
				if(secret && secret->value)
					mp->secret = g_strdup(secret->value);
				if(pin && pin->value)
					mp->pin = g_strdup(pin->value);
			} else if(!strcasecmp(type->value, "rtsp")) {
#ifndef HAVE_LIBCURL
				JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream '%s', libcurl support not compiled...\n", cat->name);
				cl = cl->next;
				continue;
#else
				janus_config_item *id = janus_config_get(config, cat, janus_config_type_item, "id");
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *priv = janus_config_get(config, cat, janus_config_type_item, "is_private");
				janus_config_item *secret = janus_config_get(config, cat, janus_config_type_item, "secret");
				janus_config_item *pin = janus_config_get(config, cat, janus_config_type_item, "pin");
				janus_config_item *file = janus_config_get(config, cat, janus_config_type_item, "url");
				janus_config_item *username = janus_config_get(config, cat, janus_config_type_item, "rtsp_user");
				janus_config_item *password = janus_config_get(config, cat, janus_config_type_item, "rtsp_pwd");
				janus_config_item *audio = janus_config_get(config, cat, janus_config_type_item, "audio");
				janus_config_item *artpmap = janus_config_get(config, cat, janus_config_type_item, "audiortpmap");
				janus_config_item *acodec = janus_config_get(config, cat, janus_config_type_item, "audiopt");
				janus_config_item *afmtp = janus_config_get(config, cat, janus_config_type_item, "audiofmtp");
				janus_config_item *video = janus_config_get(config, cat, janus_config_type_item, "video");
				janus_config_item *vcodec = janus_config_get(config, cat, janus_config_type_item, "videopt");
				janus_config_item *vrtpmap = janus_config_get(config, cat, janus_config_type_item, "videortpmap");
				janus_config_item *vfmtp = janus_config_get(config, cat, janus_config_type_item, "videofmtp");
				janus_config_item *iface = janus_config_get(config, cat, janus_config_type_item, "rtspiface");
				janus_config_item *failerr = janus_config_get(config, cat, janus_config_type_item, "rtsp_failcheck");
				janus_network_address iface_value;
				if(file == NULL || file->value == NULL) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream '%s', missing mandatory information...\n", cat->name);
					cl = cl->next;
					continue;
				}
				gboolean is_private = priv && priv->value && janus_is_true(priv->value);
				gboolean doaudio = audio && audio->value && janus_is_true(audio->value);
				gboolean dovideo = video && video->value && janus_is_true(video->value);
				gboolean error_on_failure = TRUE;
				if(failerr && failerr->value)
					error_on_failure = janus_is_true(failerr->value);

				if((doaudio || dovideo) && iface && iface->value) {
					if(!ifas) {
						JANUS_LOG(LOG_ERR, "Skipping 'rtsp' stream '%s', it relies on network configuration but network device information is unavailable...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(janus_network_lookup_interface(ifas, iface->value, &iface_value) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream '%s', invalid network interface configuration for stream...\n", cat->name);
						cl = cl->next;
						continue;
					}
				}

				if(id == NULL || id->value == NULL) {
					JANUS_LOG(LOG_VERB, "Missing id for stream '%s', will generate a random one...\n", cat->name);
				} else {
					janus_mutex_lock(&mountpoints_mutex);
					guint64 mpid = g_ascii_strtoull(id->value, 0, 10);
					janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					if(mp != NULL) {
						JANUS_LOG(LOG_ERR, "A stream with the provided ID %s already exists, skipping '%s'\n", id->value, cat->name);
						cl = cl->next;
						continue;
					}
				}
				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_rtsp_source(
						(id && id->value) ? g_ascii_strtoull(id->value, 0, 10) : 0,
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						(char *)file->value,
						username ? (char *)username->value : NULL,
						password ? (char *)password->value : NULL,
						doaudio,
						(acodec && acodec->value) ? atoi(acodec->value) : -1,
						artpmap ? (char *)artpmap->value : NULL,
						afmtp ? (char *)afmtp->value : NULL,
						dovideo,
						(vcodec && vcodec->value) ? atoi(vcodec->value) : -1,
						vrtpmap ? (char *)vrtpmap->value : NULL,
						vfmtp ? (char *)vfmtp->value : NULL,
						iface && iface->value ? &iface_value : NULL,
						error_on_failure)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'rtsp' stream '%s'...\n", cat->name);
					cl = cl->next;
					continue;
				}
				mp->is_private = is_private;
				if(secret && secret->value)
					mp->secret = g_strdup(secret->value);
				if(pin && pin->value)
					mp->pin = g_strdup(pin->value);
#endif
			} else {
				JANUS_LOG(LOG_WARN, "Ignoring unknown stream type '%s' (%s)...\n", type->value, cat->name);
			}
			cl = cl->next;
		}
		g_list_free(clist);
		/* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
	}
	if(ifas) {
		freeifaddrs(ifas);
	}

	/* Show available mountpoints */
	janus_mutex_lock(&mountpoints_mutex);
	GHashTableIter iter;
	gpointer value;
	g_hash_table_iter_init(&iter, mountpoints);
	while(g_hash_table_iter_next(&iter, NULL, &value)) {
		janus_streaming_mountpoint *mp = value;
		JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s] %s (%s, %s, %s, pin: %s)\n", mp->id, mp->name, mp->description,
			mp->streaming_type == janus_streaming_type_live ? "live" : "on demand",
			mp->streaming_source == janus_streaming_source_rtp ? "RTP source" : "file source",
			mp->is_private ? "private" : "public",
			mp->pin ? mp->pin : "no pin");
	}
	janus_mutex_unlock(&mountpoints_mutex);

	sessions = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_streaming_session_destroy);
	messages = g_async_queue_new_full((GDestroyNotify) janus_streaming_message_free);
	/* This is the callback we'll need to invoke to contact the Janus core */
	gateway = callback;

	/* Launch the thread that will handle incoming messages */
	GError *error = NULL;
	handler_thread = g_thread_try_new("streaming handler", janus_streaming_handler, NULL, &error);
	if(error != NULL) {
		g_atomic_int_set(&initialized, 0);
		JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Streaming handler thread...\n", error->code, error->message ? error->message : "??");
		janus_config_destroy(config);
		return -1;
	}
	JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_STREAMING_NAME);
	return 0;
}

void janus_streaming_destroy(void) {
	if(!g_atomic_int_get(&initialized))
		return;
	g_atomic_int_set(&stopping, 1);

	g_async_queue_push(messages, &exit_message);
	if(handler_thread != NULL) {
		g_thread_join(handler_thread);
		handler_thread = NULL;
	}

	/* Remove all mountpoints */
	janus_mutex_lock(&mountpoints_mutex);
	g_hash_table_destroy(mountpoints);
	mountpoints = NULL;
	janus_mutex_unlock(&mountpoints_mutex);
	janus_mutex_lock(&sessions_mutex);
	g_hash_table_destroy(sessions);
	sessions = NULL;
	janus_mutex_unlock(&sessions_mutex);
	g_async_queue_unref(messages);
	messages = NULL;

	janus_config_destroy(config);
	g_free(admin_key);

	g_atomic_int_set(&initialized, 0);
	g_atomic_int_set(&stopping, 0);
	JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_STREAMING_NAME);
}

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

int janus_streaming_get_version(void) {
	return JANUS_STREAMING_VERSION;
}

const char *janus_streaming_get_version_string(void) {
	return JANUS_STREAMING_VERSION_STRING;
}

const char *janus_streaming_get_description(void) {
	return JANUS_STREAMING_DESCRIPTION;
}

const char *janus_streaming_get_name(void) {
	return JANUS_STREAMING_NAME;
}

const char *janus_streaming_get_author(void) {
	return JANUS_STREAMING_AUTHOR;
}

const char *janus_streaming_get_package(void) {
	return JANUS_STREAMING_PACKAGE;
}

static janus_streaming_session *janus_streaming_lookup_session(janus_plugin_session *handle) {
	janus_streaming_session *session = NULL;
	if(g_hash_table_contains(sessions, handle)) {
		session = (janus_streaming_session *)handle->plugin_handle;
	}
	return session;
}

void janus_streaming_create_session(janus_plugin_session *handle, int *error) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
		*error = -1;
		return;
	}
	janus_streaming_session *session = g_malloc0(sizeof(janus_streaming_session));
	session->handle = handle;
	session->mountpoint = NULL;	/* This will happen later */
	session->started = FALSE;	/* This will happen later */
	session->paused = FALSE;
	g_atomic_int_set(&session->destroyed, 0);
	g_atomic_int_set(&session->hangingup, 0);
	handle->plugin_handle = session;
	janus_refcount_init(&session->ref, janus_streaming_session_free);
	janus_mutex_lock(&sessions_mutex);
	g_hash_table_insert(sessions, handle, session);
	janus_mutex_unlock(&sessions_mutex);

	return;
}

void janus_streaming_destroy_session(janus_plugin_session *handle, int *error) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
		*error = -1;
		return;
	}
	janus_mutex_lock(&sessions_mutex);
	janus_streaming_session *session = janus_streaming_lookup_session(handle);
	if(!session) {
		janus_mutex_unlock(&sessions_mutex);
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		*error = -2;
		return;
	}
	JANUS_LOG(LOG_VERB, "Removing streaming session...\n");
	janus_streaming_hangup_media_internal(handle);
	g_hash_table_remove(sessions, handle);
	janus_mutex_unlock(&sessions_mutex);
	return;
}

json_t *janus_streaming_query_session(janus_plugin_session *handle) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
		return NULL;
	}
	janus_mutex_lock(&sessions_mutex);
	janus_streaming_session *session = janus_streaming_lookup_session(handle);
	if(!session) {
		janus_mutex_unlock(&sessions_mutex);
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		return NULL;
	}
	janus_refcount_increase(&session->ref);
	janus_mutex_unlock(&sessions_mutex);
	/* What is this user watching, if anything? */
	json_t *info = json_object();
	janus_streaming_mountpoint *mp = session->mountpoint;
	json_object_set_new(info, "state", json_string(mp ? "watching" : "idle"));
	if(mp) {
		janus_refcount_increase(&mp->ref);
		json_object_set_new(info, "mountpoint_id", json_integer(mp->id));
		json_object_set_new(info, "mountpoint_name", mp->name ? json_string(mp->name) : NULL);
		janus_mutex_lock(&mp->mutex);
		json_object_set_new(info, "mountpoint_viewers", json_integer(mp->viewers ? g_list_length(mp->viewers) : 0));
		janus_mutex_unlock(&mp->mutex);
		json_t *media = json_object();
		json_object_set_new(media, "audio", session->audio ? json_true() : json_false());
		json_object_set_new(media, "video", session->video ? json_true() : json_false());
		json_object_set_new(media, "data", session->data ? json_true() : json_false());
		json_object_set_new(info, "media", media);
		if(mp->streaming_source == janus_streaming_source_rtp) {
			janus_streaming_rtp_source *source = mp->source;
			if(source->simulcast) {
				json_t *simulcast = json_object();
				json_object_set_new(simulcast, "substream", json_integer(session->sim_context.substream));
				json_object_set_new(simulcast, "substream-target", json_integer(session->sim_context.substream_target));
				json_object_set_new(simulcast, "temporal-layer", json_integer(session->sim_context.templayer));
				json_object_set_new(simulcast, "temporal-layer-target", json_integer(session->sim_context.templayer_target));
				json_object_set_new(info, "simulcast", simulcast);
			}
			if(source->svc) {
				json_t *svc = json_object();
				json_object_set_new(svc, "spatial-layer", json_integer(session->spatial_layer));
				json_object_set_new(svc, "target-spatial-layer", json_integer(session->target_spatial_layer));
				json_object_set_new(svc, "temporal-layer", json_integer(session->temporal_layer));
				json_object_set_new(svc, "target-temporal-layer", json_integer(session->target_temporal_layer));
				json_object_set_new(info, "svc", svc);
			}
		}
		janus_refcount_decrease(&mp->ref);
	}
	json_object_set_new(info, "hangingup", json_integer(g_atomic_int_get(&session->hangingup)));
	json_object_set_new(info, "destroyed", json_integer(g_atomic_int_get(&session->destroyed)));
	janus_refcount_decrease(&session->ref);
	return info;
}

/* Helper method to process synchronous requests */
static json_t *janus_streaming_process_synchronous_request(janus_streaming_session *session, json_t *message) {
	json_t *request = json_object_get(message, "request");
	const char *request_text = json_string_value(request);

	/* Parse the message */
	int error_code = 0;
	char error_cause[512];
	json_t *root = message;
	json_t *response = NULL;
	struct ifaddrs *ifas = NULL;

	if(!strcasecmp(request_text, "list")) {
		json_t *list = json_array();
		JANUS_LOG(LOG_VERB, "Request for the list of mountpoints\n");
		/* Return a list of all available mountpoints */
		janus_mutex_lock(&mountpoints_mutex);
		GHashTableIter iter;
		gpointer value;
		g_hash_table_iter_init(&iter, mountpoints);
		while(g_hash_table_iter_next(&iter, NULL, &value)) {
			janus_streaming_mountpoint *mp = value;
			if(mp->is_private) {
				/* Skip private stream */
				JANUS_LOG(LOG_VERB, "Skipping private mountpoint '%s'\n", mp->description);
				continue;
			}
			janus_refcount_increase(&mp->ref);
			json_t *ml = json_object();
			json_object_set_new(ml, "id", json_integer(mp->id));
			json_object_set_new(ml, "description", json_string(mp->description));
			json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
			if(mp->streaming_source == janus_streaming_source_rtp) {
				janus_streaming_rtp_source *source = mp->source;
				gint64 now = janus_get_monotonic_time();
				if(source->audio_fd != -1)
					json_object_set_new(ml, "audio_age_ms", json_integer((now - source->last_received_audio) / 1000));
				if(source->video_fd[0] != -1 || source->video_fd[1] != -1 || source->video_fd[2] != -1)
					json_object_set_new(ml, "video_age_ms", json_integer((now - source->last_received_video) / 1000));
			}
			json_array_append_new(list, ml);
			janus_refcount_decrease(&mp->ref);
		}
		janus_mutex_unlock(&mountpoints_mutex);
		/* Send info back */
		response = json_object();
		json_object_set_new(response, "streaming", json_string("list"));
		json_object_set_new(response, "list", list);
		goto prepare_response;
	} else if(!strcasecmp(request_text, "info")) {
		JANUS_LOG(LOG_VERB, "Request info on a specific mountpoint\n");
		/* Return info on a specific mountpoint */
		JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto prepare_response;
		json_t *id = json_object_get(root, "id");
		guint64 id_value = json_integer_value(id);
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %"SCNu64"", id_value);
			goto prepare_response;
		}
		janus_refcount_increase(&mp->ref);
		/* Return more info if the right secret is provided */
		gboolean admin = FALSE;
		if(mp->secret) {
			json_t *secret = json_object_get(root, "secret");
			if(secret && json_string_value(secret) && janus_strcmp_const_time(mp->secret, json_string_value(secret)))
				admin = TRUE;
		} else {
			admin = TRUE;
		}
		json_t *ml = json_object();
		json_object_set_new(ml, "id", json_integer(mp->id));
		if(admin && mp->name)
			json_object_set_new(ml, "name", json_string(mp->name));
		if(mp->description)
			json_object_set_new(ml, "description", json_string(mp->description));
		if(admin && mp->secret)
			json_object_set_new(ml, "secret", json_string(mp->secret));
		if(admin && mp->pin)
			json_object_set_new(ml, "pin", json_string(mp->pin));
		if(admin && mp->is_private)
			json_object_set_new(ml, "is_private", json_true());
		json_object_set_new(ml, "enabled", mp->enabled ? json_true() : json_false());
		if(mp->audio) {
			json_object_set_new(ml, "audio", json_true());
			if(mp->codecs.audio_pt != -1)
				json_object_set_new(ml, "audiopt", json_integer(mp->codecs.audio_pt));
			if(mp->codecs.audio_rtpmap)
				json_object_set_new(ml, "audiortpmap", json_string(mp->codecs.audio_rtpmap));
			if(mp->codecs.audio_fmtp)
				json_object_set_new(ml, "audiofmtp", json_string(mp->codecs.audio_fmtp));
		}
		if(mp->video) {
			json_object_set_new(ml, "video", json_true());
			if(mp->codecs.video_pt != -1)
				json_object_set_new(ml, "videopt", json_integer(mp->codecs.video_pt));
			if(mp->codecs.video_rtpmap)
				json_object_set_new(ml, "videortpmap", json_string(mp->codecs.video_rtpmap));
			if(mp->codecs.video_fmtp)
				json_object_set_new(ml, "videofmtp", json_string(mp->codecs.video_fmtp));
		}
		if(mp->data) {
			json_object_set_new(ml, "data", json_true());
		}
		json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
		if(mp->streaming_source == janus_streaming_source_file) {
			janus_streaming_file_source *source = mp->source;
			if(admin && source->filename)
				json_object_set_new(ml, "filename", json_string(source->filename));
		} else if(mp->streaming_source == janus_streaming_source_rtp) {
			janus_streaming_rtp_source *source = mp->source;
			if(source->is_srtp) {
				json_object_set_new(ml, "srtp", json_true());
			}
			gint64 now = janus_get_monotonic_time();
#ifdef HAVE_LIBCURL
			if(source->rtsp) {
				json_object_set_new(ml, "rtsp", json_true());
				if(admin) {
					if(source->rtsp_url)
						json_object_set_new(ml, "url", json_string(source->rtsp_url));
					if(source->rtsp_username)
						json_object_set_new(ml, "rtsp_user", json_string(source->rtsp_username));
					if(source->rtsp_password)
						json_object_set_new(ml, "rtsp_pwd", json_string(source->rtsp_password));
				}
			}
#endif
			if(source->keyframe.enabled) {
				json_object_set_new(ml, "videobufferkf", json_true());
			}
			if(source->simulcast) {
				json_object_set_new(ml, "videosimulcast", json_true());
			}
			if(source->svc) {
				json_object_set_new(ml, "videosvc", json_true());
			}
			if(source->askew)
				json_object_set_new(ml, "audioskew", json_true());
			if(source->vskew)
				json_object_set_new(ml, "videoskew", json_true());
			if(source->rtp_collision > 0)
				json_object_set_new(ml, "collision", json_integer(source->rtp_collision));
			if(mp->helper_threads > 0)
				json_object_set_new(ml, "threads", json_integer(mp->helper_threads));
			if(admin) {
				if(mp->audio) {
					json_object_set_new(ml, "audioport", json_integer(source->audio_port));
					if(source->audio_rtcp_port > -1)
						json_object_set_new(ml, "audiortcpport", json_integer(source->audio_rtcp_port));
				}
				if(mp->video) {
					json_object_set_new(ml, "videoport", json_integer(source->video_port[0]));
					if(source->video_rtcp_port > -1)
						json_object_set_new(ml, "videortcpport", json_integer(source->video_rtcp_port));
					if(source->video_port[1] > -1)
						json_object_set_new(ml, "videoport2", json_integer(source->video_port[1]));
					if(source->video_port[2] > -1)
						json_object_set_new(ml, "videoport3", json_integer(source->video_port[2]));
				}
				if(mp->data)
					json_object_set_new(ml, "dataport", json_integer(source->data_port));
			}
			if(source->audio_fd != -1)
				json_object_set_new(ml, "audio_age_ms", json_integer((now - source->last_received_audio) / 1000));
			if(source->video_fd[0] != -1 || source->video_fd[1] != -1 || source->video_fd[2] != -1)
				json_object_set_new(ml, "video_age_ms", json_integer((now - source->last_received_video) / 1000));
			if(source->data_fd != -1)
				json_object_set_new(ml, "data_age_ms", json_integer((now - source->last_received_data) / 1000));
			janus_mutex_lock(&source->rec_mutex);
			if(admin && (source->arc || source->vrc || source->drc)) {
				json_t *recording = json_object();
				if(source->arc && source->arc->filename)
					json_object_set_new(recording, "audio", json_string(source->arc->filename));
				if(source->vrc && source->vrc->filename)
					json_object_set_new(recording, "video", json_string(source->vrc->filename));
				if(source->drc && source->drc->filename)
					json_object_set_new(recording, "data", json_string(source->drc->filename));
				json_object_set_new(ml, "recording", recording);
			}
			janus_mutex_unlock(&source->rec_mutex);
		}
		janus_refcount_decrease(&mp->ref);
		janus_mutex_unlock(&mountpoints_mutex);
		/* Send info back */
		response = json_object();
		json_object_set_new(response, "streaming", json_string("info"));
		json_object_set_new(response, "info", ml);
		goto prepare_response;
	} else if(!strcasecmp(request_text, "create")) {
		/* Create a new stream */
		JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto prepare_response;
		if(admin_key != NULL) {
			/* An admin key was specified: make sure it was provided, and that it's valid */
			JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto prepare_response;
			JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
			if(error_code != 0)
				goto prepare_response;
		}

		if(getifaddrs(&ifas) == -1) {
			JANUS_LOG(LOG_ERR, "Unable to acquire list of network devices/interfaces; some configurations may not work as expected...\n");
		}

		json_t *type = json_object_get(root, "type");
		const char *type_text = json_string_value(type);
		json_t *secret = json_object_get(root, "secret");
		json_t *pin = json_object_get(root, "pin");
		json_t *permanent = json_object_get(root, "permanent");
		gboolean save = permanent ? json_is_true(permanent) : FALSE;
		if(save && config == NULL) {
			JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent mountpoint\n");
			error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
			g_snprintf(error_cause, 512, "No configuration file, can't create permanent mountpoint");
			goto prepare_response;
		}
		janus_streaming_mountpoint *mp = NULL;
		if(!strcasecmp(type_text, "rtp")) {
			janus_network_address audio_iface, video_iface, data_iface;
			/* RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
			JANUS_VALIDATE_JSON_OBJECT(root, rtp_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto prepare_response;
			json_t *id = json_object_get(root, "id");
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *is_private = json_object_get(root, "is_private");
			json_t *audio = json_object_get(root, "audio");
			json_t *video = json_object_get(root, "video");
			json_t *data = json_object_get(root, "data");
			json_t *rtpcollision = json_object_get(root, "collision");
			json_t *threads = json_object_get(root, "threads");
			json_t *ssuite = json_object_get(root, "srtpsuite");
			json_t *scrypto = json_object_get(root, "srtpcrypto");
			gboolean doaudio = audio ? json_is_true(audio) : FALSE;
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			gboolean dodata = data ? json_is_true(data) : FALSE;
			gboolean doaskew = FALSE, dovskew = FALSE, dosvc = FALSE;
			if(!doaudio && !dovideo && !dodata) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, no audio, video or data have to be streamed...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtp' stream, no audio or video have to be streamed...");
				goto prepare_response;
			}
			if(ssuite && json_integer_value(ssuite) != 32 && json_integer_value(ssuite) != 80) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, invalid SRTP suite...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtp' stream, invalid SRTP suite...");
				goto prepare_response;
			}
			uint16_t aport = 0;
			uint16_t artcpport = 0;
			uint8_t acodec = 0;
			char *artpmap = NULL, *afmtp = NULL, *amcast = NULL;
			if(doaudio) {
				JANUS_VALIDATE_JSON_OBJECT(root, rtp_audio_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
				if(error_code != 0)
					goto prepare_response;
				json_t *audiomcast = json_object_get(root, "audiomcast");
				amcast = (char *)json_string_value(audiomcast);
				json_t *audioport = json_object_get(root, "audioport");
				aport = json_integer_value(audioport);
				json_t *audiortcpport = json_object_get(root, "audiortcpport");
				if(audiortcpport)
					artcpport = json_integer_value(audiortcpport);
				json_t *audiopt = json_object_get(root, "audiopt");
				acodec = json_integer_value(audiopt);
				json_t *audiortpmap = json_object_get(root, "audiortpmap");
				artpmap = (char *)json_string_value(audiortpmap);
				json_t *audiofmtp = json_object_get(root, "audiofmtp");
				afmtp = (char *)json_string_value(audiofmtp);
				json_t *aiface = json_object_get(root, "audioiface");
				if(aiface) {
					const char *miface = (const char *)json_string_value(aiface);
					if(janus_network_lookup_interface(ifas, miface, &audio_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for audio...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, ifas ? "Invalid network interface configuration for audio" : "Unable to query network device information");
						goto prepare_response;
					}
				} else {
					janus_network_address_nullify(&audio_iface);
				}
				json_t *askew = json_object_get(root, "audioskew");
				doaskew = askew ? json_is_true(askew) : FALSE;
			}
			uint16_t vport = 0, vport2 = 0, vport3 = 0;
			uint16_t vrtcpport = 0;
			uint8_t vcodec = 0;
			char *vrtpmap = NULL, *vfmtp = NULL, *vmcast = NULL;
			gboolean bufferkf = FALSE, simulcast = FALSE;
			if(dovideo) {
				JANUS_VALIDATE_JSON_OBJECT(root, rtp_video_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
				if(error_code != 0)
					goto prepare_response;
				json_t *videomcast = json_object_get(root, "videomcast");
				vmcast = (char *)json_string_value(videomcast);
				json_t *videoport = json_object_get(root, "videoport");
				vport = json_integer_value(videoport);
				json_t *videortcpport = json_object_get(root, "videortcpport");
				if(videortcpport)
					vrtcpport = json_integer_value(videortcpport);
				json_t *videopt = json_object_get(root, "videopt");
				vcodec = json_integer_value(videopt);
				json_t *videortpmap = json_object_get(root, "videortpmap");
				vrtpmap = (char *)json_string_value(videortpmap);
				json_t *videofmtp = json_object_get(root, "videofmtp");
				vfmtp = (char *)json_string_value(videofmtp);
				json_t *vkf = json_object_get(root, "videobufferkf");
				bufferkf = vkf ? json_is_true(vkf) : FALSE;
				json_t *vsc = json_object_get(root, "videosimulcast");
				simulcast = vsc ? json_is_true(vsc) : FALSE;
				if(simulcast && bufferkf) {
					/* FIXME We'll need to take care of this */
					JANUS_LOG(LOG_WARN, "Simulcasting enabled, so disabling buffering of keyframes\n");
					bufferkf = FALSE;
				}
				json_t *videoport2 = json_object_get(root, "videoport2");
				vport2 = json_integer_value(videoport2);
				json_t *videoport3 = json_object_get(root, "videoport3");
				vport3 = json_integer_value(videoport3);
				json_t *viface = json_object_get(root, "videoiface");
				if(viface) {
					const char *miface = (const char *)json_string_value(viface);
					if(janus_network_lookup_interface(ifas, miface, &video_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for video...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, ifas ? "Invalid network interface configuration for video" : "Unable to query network device information");
						goto prepare_response;
					}
				} else {
					janus_network_address_nullify(&video_iface);
				}
				json_t *vskew = json_object_get(root, "videoskew");
				dovskew = vskew ? json_is_true(vskew) : FALSE;
				json_t *vsvc = json_object_get(root, "videosvc");
				dosvc = vsvc ? json_is_true(vsvc) : FALSE;
			}
			uint16_t dport = 0;
			gboolean buffermsg = FALSE;
			if(dodata) {
				JANUS_VALIDATE_JSON_OBJECT(root, rtp_data_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
				if(error_code != 0)
					goto prepare_response;
#ifdef HAVE_SCTP
				json_t *dataport = json_object_get(root, "dataport");
				dport = json_integer_value(dataport);
				json_t *dbm = json_object_get(root, "databuffermsg");
				buffermsg = dbm ? json_is_true(dbm) : FALSE;
				json_t *diface = json_object_get(root, "dataiface");
				if(diface) {
					const char *miface = (const char *)json_string_value(diface);
					if(janus_network_lookup_interface(ifas, miface, &data_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for data...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, ifas ? "Invalid network interface configuration for data" : "Unable to query network device information");
						goto prepare_response;
					}
				} else {
					janus_network_address_nullify(&data_iface);
				}
#else
				JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream: no datachannels support...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtp' stream: no datachannels support...");
				goto prepare_response;
#endif
			}
			if(id == NULL) {
				JANUS_LOG(LOG_VERB, "Missing id, will generate a random one...\n");
			} else {
				janus_mutex_lock(&mountpoints_mutex);
				guint64 mpid = json_integer_value(id);
				mp = g_hash_table_lookup(mountpoints, &mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				if(mp != NULL) {
					JANUS_LOG(LOG_ERR, "A stream with the provided ID already exists\n");
					error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
					g_snprintf(error_cause, 512, "A stream with the provided ID already exists");
					goto prepare_response;
				}
			}
			JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled");
			mp = janus_streaming_create_rtp_source(
					id ? json_integer_value(id) : 0,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					ssuite ? json_integer_value(ssuite) : 0,
					scrypto ? (char *)json_string_value(scrypto) : NULL,
					threads ? json_integer_value(threads) : 0,
					doaudio, amcast, &audio_iface, aport, artcpport, acodec, artpmap, afmtp, doaskew,
					dovideo, vmcast, &video_iface, vport, vrtcpport, vcodec, vrtpmap, vfmtp, bufferkf,
					simulcast, vport2, vport3, dosvc, dovskew,
					rtpcollision ? json_integer_value(rtpcollision) : 0,
					dodata, &data_iface, dport, buffermsg);
			if(mp == NULL) {
				JANUS_LOG(LOG_ERR, "Error creating 'rtp' stream...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Error creating 'rtp' stream");
				goto prepare_response;
			}
			mp->is_private = is_private ? json_is_true(is_private) : FALSE;
		} else if(!strcasecmp(type_text, "live")) {
			/* File live source */
			JANUS_VALIDATE_JSON_OBJECT(root, live_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto prepare_response;
			json_t *id = json_object_get(root, "id");
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *is_private = json_object_get(root, "is_private");
			json_t *file = json_object_get(root, "filename");
			json_t *audio = json_object_get(root, "audio");
			json_t *video = json_object_get(root, "video");
			gboolean doaudio = audio ? json_is_true(audio) : FALSE;
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			/* TODO We should support something more than raw a-Law and mu-Law streams... */
			if(!doaudio || dovideo) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, we only support audio file streaming right now...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'live' stream, we only support audio file streaming right now...");
				goto prepare_response;
			}
			char *filename = (char *)json_string_value(file);
			if(!strstr(filename, ".alaw") && !strstr(filename, ".mulaw")) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'live' stream, unsupported format (we only support raw mu-Law and a-Law files right now)");
				goto prepare_response;
			}
			FILE *audiofile = fopen(filename, "rb");
			if(!audiofile) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, no such file '%s'...\n", filename);
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'live' stream, no such file '%s'\n", filename);
				goto prepare_response;
			}
			fclose(audiofile);
			if(id == NULL) {
				JANUS_LOG(LOG_VERB, "Missing id, will generate a random one...\n");
			} else {
				janus_mutex_lock(&mountpoints_mutex);
				guint64 mpid = json_integer_value(id);
				mp = g_hash_table_lookup(mountpoints, &mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				if(mp != NULL) {
					JANUS_LOG(LOG_ERR, "A stream with the provided ID already exists\n");
					error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
					g_snprintf(error_cause, 512, "A stream with the provided ID already exists");
					goto prepare_response;
				}
			}
			mp = janus_streaming_create_file_source(
					id ? json_integer_value(id) : 0,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					filename,
					TRUE, doaudio, dovideo);
			if(mp == NULL) {
				JANUS_LOG(LOG_ERR, "Error creating 'live' stream...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Error creating 'live' stream");
				goto prepare_response;
			}
			mp->is_private = is_private ? json_is_true(is_private) : FALSE;
		} else if(!strcasecmp(type_text, "ondemand")) {
			/* mu-Law file on demand source */
			JANUS_VALIDATE_JSON_OBJECT(root, ondemand_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto prepare_response;
			json_t *id = json_object_get(root, "id");
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *is_private = json_object_get(root, "is_private");
			json_t *file = json_object_get(root, "filename");
			json_t *audio = json_object_get(root, "audio");
			json_t *video = json_object_get(root, "video");
			gboolean doaudio = audio ? json_is_true(audio) : FALSE;
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			/* TODO We should support something more than raw a-Law and mu-Law streams... */
			if(!doaudio || dovideo) {
				JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, we only support audio file streaming right now...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'ondemand' stream, we only support audio file streaming right now...");
				goto prepare_response;
			}
			char *filename = (char *)json_string_value(file);
			if(!strstr(filename, ".alaw") && !strstr(filename, ".mulaw")) {
				JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'ondemand' stream, unsupported format (we only support raw mu-Law and a-Law files right now)");
				goto prepare_response;
			}
			FILE *audiofile = fopen(filename, "rb");
			if(!audiofile) {
				JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, no such file '%s'...\n", filename);
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'ondemand' stream, no such file '%s'\n", filename);
				goto prepare_response;
			}
			fclose(audiofile);
			if(id == NULL) {
				JANUS_LOG(LOG_VERB, "Missing id, will generate a random one...\n");
			} else {
				janus_mutex_lock(&mountpoints_mutex);
				guint64 mpid = json_integer_value(id);
				mp = g_hash_table_lookup(mountpoints, &mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				if(mp != NULL) {
					JANUS_LOG(LOG_ERR, "A stream with the provided ID already exists\n");
					error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
					g_snprintf(error_cause, 512, "A stream with the provided ID already exists");
					goto prepare_response;
				}
			}
			mp = janus_streaming_create_file_source(
					id ? json_integer_value(id) : 0,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					filename,
					FALSE, doaudio, dovideo);
			if(mp == NULL) {
				JANUS_LOG(LOG_ERR, "Error creating 'ondemand' stream...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Error creating 'ondemand' stream");
				goto prepare_response;
			}
			mp->is_private = is_private ? json_is_true(is_private) : FALSE;
		} else if(!strcasecmp(type_text, "rtsp")) {
#ifndef HAVE_LIBCURL
			JANUS_LOG(LOG_ERR, "Can't create 'rtsp' mountpoint, libcurl support not compiled...\n");
			error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
			g_snprintf(error_cause, 512, "Can't create 'rtsp' mountpoint, libcurl support not compiled...\n");
			goto prepare_response;
#else
			JANUS_VALIDATE_JSON_OBJECT(root, rtsp_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto prepare_response;
			/* RTSP source*/
			janus_network_address multicast_iface;
			json_t *id = json_object_get(root, "id");
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *is_private = json_object_get(root, "is_private");
			json_t *audio = json_object_get(root, "audio");
			json_t *audiopt = json_object_get(root, "audiopt");
			json_t *audiortpmap = json_object_get(root, "audiortpmap");
			json_t *audiofmtp = json_object_get(root, "audiofmtp");
			json_t *video = json_object_get(root, "video");
			json_t *videopt = json_object_get(root, "videopt");
			json_t *videortpmap = json_object_get(root, "videortpmap");
			json_t *videofmtp = json_object_get(root, "videofmtp");
			json_t *url = json_object_get(root, "url");
			json_t *username = json_object_get(root, "rtsp_user");
			json_t *password = json_object_get(root, "rtsp_pwd");
			json_t *iface = json_object_get(root, "rtspiface");
			json_t *failerr = json_object_get(root, "rtsp_check");
			gboolean doaudio = audio ? json_is_true(audio) : FALSE;
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			gboolean error_on_failure = failerr ? json_is_true(failerr) : TRUE;
			if(!doaudio && !dovideo) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream, no audio or video have to be streamed...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtsp' stream, no audio or video have to be streamed...");
				goto prepare_response;
			} else {
				if(iface) {
					const char *miface = (const char *)json_string_value(iface);
					if(janus_network_lookup_interface(ifas, miface, &multicast_iface) != 0) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream '%s', invalid network interface configuration for stream...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, ifas ? "Invalid network interface configuration for stream" : "Unable to query network device information");
						goto prepare_response;
					}
				} else {
					janus_network_address_nullify(&multicast_iface);
				}
			}
			mp = janus_streaming_create_rtsp_source(
					id ? json_integer_value(id) : 0,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					(char *)json_string_value(url),
					username ? (char *)json_string_value(username) : NULL,
					password ? (char *)json_string_value(password) : NULL,
					doaudio, (audiopt ? json_integer_value(audiopt) : -1),
						(char *)json_string_value(audiortpmap), (char *)json_string_value(audiofmtp),
					dovideo, (videopt ? json_integer_value(videopt) : -1),
						(char *)json_string_value(videortpmap), (char *)json_string_value(videofmtp),
					&multicast_iface,
					error_on_failure);
			if(mp == NULL) {
				JANUS_LOG(LOG_ERR, "Error creating 'rtsp' stream...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Error creating 'RTSP' stream");
				goto prepare_response;
			}
			mp->is_private = is_private ? json_is_true(is_private) : FALSE;
#endif
		} else {
			JANUS_LOG(LOG_ERR, "Unknown stream type '%s'...\n", type_text);
			error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
			g_snprintf(error_cause, 512, "Unknown stream type '%s'...\n", type_text);
			goto prepare_response;
		}
		/* Any secret? */
		if(secret)
			mp->secret = g_strdup(json_string_value(secret));
		/* Any PIN? */
		if(pin)
			mp->pin = g_strdup(json_string_value(pin));
		if(save) {
			/* This mountpoint is permanent: save to the configuration file too
			 * FIXME: We should check if anything fails... */
			JANUS_LOG(LOG_VERB, "Saving mountpoint %"SCNu64" permanently in config file\n", mp->id);
			janus_mutex_lock(&config_mutex);
			char value[BUFSIZ];
			/* The category to add is the mountpoint name */
			janus_config_category *c = janus_config_get_create(config, NULL, janus_config_type_category, mp->name);
			/* Now for the common values */
			janus_config_add(config, c, janus_config_item_create("type", type_text));
			g_snprintf(value, BUFSIZ, "%"SCNu64, mp->id);
			janus_config_add(config, c, janus_config_item_create("id", value));
			janus_config_add(config, c, janus_config_item_create("description", mp->description));
			if(mp->is_private)
				janus_config_add(config, c, janus_config_item_create("is_private", "yes"));
			/* Per type values */
			if(!strcasecmp(type_text, "rtp")) {
				janus_config_add(config, c, janus_config_item_create("audio", mp->codecs.audio_pt >= 0 ? "yes" : "no"));
				janus_streaming_rtp_source *source = mp->source;
				if(mp->codecs.audio_pt >= 0) {
					g_snprintf(value, BUFSIZ, "%d", source->audio_port);
					janus_config_add(config, c, janus_config_item_create("audioport", value));
					if(source->audio_rtcp_port > 0) {
						g_snprintf(value, BUFSIZ, "%d", source->audio_rtcp_port);
						janus_config_add(config, c, janus_config_item_create("audiortcpport", value));
					}
					json_t *audiomcast = json_object_get(root, "audiomcast");
					if(audiomcast)
						janus_config_add(config, c, janus_config_item_create("audiomcast", json_string_value(audiomcast)));
					g_snprintf(value, BUFSIZ, "%d", mp->codecs.audio_pt);
					janus_config_add(config, c, janus_config_item_create("audiopt", value));
					janus_config_add(config, c, janus_config_item_create("audiortpmap", mp->codecs.audio_rtpmap));
					if(mp->codecs.audio_fmtp)
						janus_config_add(config, c, janus_config_item_create("audiofmtp", mp->codecs.audio_fmtp));
					json_t *aiface = json_object_get(root, "audioiface");
					if(aiface)
						janus_config_add(config, c, janus_config_item_create("audioiface", json_string_value(aiface)));
					if(source->askew)
						janus_config_add(config, c, janus_config_item_create("askew", "yes"));
				}
				janus_config_add(config, c, janus_config_item_create("video", mp->codecs.video_pt > 0 ? "yes" : "no"));
				if(mp->codecs.video_pt > 0) {
					g_snprintf(value, BUFSIZ, "%d", source->video_port[0]);
					janus_config_add(config, c, janus_config_item_create("videoport", value));
					if(source->video_rtcp_port > 0) {
						g_snprintf(value, BUFSIZ, "%d", source->video_rtcp_port);
						janus_config_add(config, c, janus_config_item_create("videortcpport", value));
					}
					json_t *videomcast = json_object_get(root, "videomcast");
					if(videomcast)
						janus_config_add(config, c, janus_config_item_create("videomcast", json_string_value(videomcast)));
					g_snprintf(value, BUFSIZ, "%d", mp->codecs.video_pt);
					janus_config_add(config, c, janus_config_item_create("videopt", value));
					janus_config_add(config, c, janus_config_item_create("videortpmap", mp->codecs.video_rtpmap));
					if(mp->codecs.video_fmtp)
						janus_config_add(config, c, janus_config_item_create("videofmtp", mp->codecs.video_fmtp));
					if(source->keyframe.enabled)
						janus_config_add(config, c, janus_config_item_create("videobufferkf", "yes"));
					if(source->simulcast) {
						janus_config_add(config, c, janus_config_item_create("videosimulcast", "yes"));
						if(source->video_port[1]) {
							g_snprintf(value, BUFSIZ, "%d", source->video_port[1]);
							janus_config_add(config, c, janus_config_item_create("videoport2", value));
						}
						if(source->video_port[2]) {
							g_snprintf(value, BUFSIZ, "%d", source->video_port[2]);
							janus_config_add(config, c, janus_config_item_create("videoport3", value));
						}
					}
					if(source->svc)
						janus_config_add(config, c, janus_config_item_create("videosvc", "yes"));
					json_t *viface = json_object_get(root, "videoiface");
					if(viface)
						janus_config_add(config, c, janus_config_item_create("videoiface", json_string_value(viface)));
					if(source->vskew)
						janus_config_add(config, c, janus_config_item_create("videoskew", "yes"));
				}
				if(source->rtp_collision > 0) {
					g_snprintf(value, BUFSIZ, "%d", source->rtp_collision);
					janus_config_add(config, c, janus_config_item_create("collision", value));
				}
				janus_config_add(config, c, janus_config_item_create("data", mp->data ? "yes" : "no"));
				if(source->data_port > -1) {
					g_snprintf(value, BUFSIZ, "%d", source->data_port);
					janus_config_add(config, c, janus_config_item_create("dataport", value));
					if(source->buffermsg)
						janus_config_add(config, c, janus_config_item_create("databuffermsg", "yes"));
					json_t *diface = json_object_get(root, "dataiface");
					if(diface)
						janus_config_add(config, c, janus_config_item_create("dataiface", json_string_value(diface)));
				}
				if(source->srtpsuite > 0 && source->srtpcrypto) {
					g_snprintf(value, BUFSIZ, "%d", source->srtpsuite);
					janus_config_add(config, c, janus_config_item_create("srtpsuite", value));
					janus_config_add(config, c, janus_config_item_create("srtpcrypto", source->srtpcrypto));
				}
				if(mp->helper_threads > 0) {
					g_snprintf(value, BUFSIZ, "%d", mp->helper_threads);
					janus_config_add(config, c, janus_config_item_create("threads", value));
				}
			} else if(!strcasecmp(type_text, "live") || !strcasecmp(type_text, "ondemand")) {
				janus_streaming_file_source *source = mp->source;
				janus_config_add(config, c, janus_config_item_create("filename", source->filename));
				janus_config_add(config, c, janus_config_item_create("audio", mp->codecs.audio_pt >= 0 ? "yes" : "no"));
				janus_config_add(config, c, janus_config_item_create("video", mp->codecs.video_pt > 0 ? "yes" : "no"));
			} else if(!strcasecmp(type_text, "rtsp")) {
#ifdef HAVE_LIBCURL
				janus_streaming_rtp_source *source = mp->source;
				if(source->rtsp_url)
					janus_config_add(config, c, janus_config_item_create("url", source->rtsp_url));
				if(source->rtsp_username)
					janus_config_add(config, c, janus_config_item_create("rtsp_user", source->rtsp_username));
				if(source->rtsp_password)
					janus_config_add(config, c, janus_config_item_create("rtsp_pwd", source->rtsp_password));
#endif
				janus_config_add(config, c, janus_config_item_create("audio", mp->codecs.audio_pt >= 0 ? "yes" : "no"));
				if(mp->codecs.audio_pt >= 0) {
					if(mp->codecs.audio_rtpmap)
						janus_config_add(config, c, janus_config_item_create("audiortpmap", mp->codecs.audio_rtpmap));
					if(mp->codecs.audio_fmtp)
						janus_config_add(config, c, janus_config_item_create("audiofmtp", mp->codecs.audio_fmtp));
				}
				janus_config_add(config, c, janus_config_item_create("video", mp->codecs.video_pt > 0 ? "yes" : "no"));
				if(mp->codecs.video_pt > 0) {
					if(mp->codecs.video_rtpmap)
						janus_config_add(config, c, janus_config_item_create("videortpmap", mp->codecs.video_rtpmap));
					if(mp->codecs.video_fmtp)
						janus_config_add(config, c, janus_config_item_create("videofmtp", mp->codecs.video_fmtp));
				}
				json_t *iface = json_object_get(root, "rtspiface");
				if(iface)
					janus_config_add(config, c, janus_config_item_create("rtspiface", json_string_value(iface)));
			}
			/* Some more common values */
			if(mp->secret)
				janus_config_add(config, c, janus_config_item_create("secret", mp->secret));
			if(mp->pin)
				janus_config_add(config, c, janus_config_item_create("pin", mp->pin));
			/* Save modified configuration */
			if(janus_config_save(config, config_folder, JANUS_STREAMING_PACKAGE) < 0)
				save = FALSE;	/* This will notify the user the mountpoint is not permanent */
			janus_mutex_unlock(&config_mutex);
		}
		/* Send info back */
		response = json_object();
		json_object_set_new(response, "streaming", json_string("created"));
		json_object_set_new(response, "created", json_string(mp->name));
		json_object_set_new(response, "permanent", save ? json_true() : json_false());
		json_t *ml = json_object();
		json_object_set_new(ml, "id", json_integer(mp->id));
		json_object_set_new(ml, "description", json_string(mp->description));
		json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
		json_object_set_new(ml, "is_private", mp->is_private ? json_true() : json_false());
		if(!strcasecmp(type_text, "rtp")) {
			janus_streaming_rtp_source *source = mp->source;
			if(source->audio_fd != -1) {
				json_object_set_new(ml, "audio_port", json_integer(source->audio_port));
			}
			if(source->audio_rtcp_fd != -1) {
				json_object_set_new(ml, "audio_rtcp_port", json_integer(source->audio_rtcp_port));
			}
			if(source->video_fd[0] != -1) {
				json_object_set_new(ml, "video_port", json_integer(source->video_port[0]));
			}
			if(source->video_rtcp_fd != -1) {
				json_object_set_new(ml, "video_rtcp_port", json_integer(source->video_rtcp_port));
			}
			if(source->video_fd[1] != -1) {
				json_object_set_new(ml, "video_port_2", json_integer(source->video_port[1]));
			}
			if(source->video_fd[2] != -1) {
				json_object_set_new(ml, "video_port_3", json_integer(source->video_port[2]));
			}
			if(source->data_fd != -1) {
				json_object_set_new(ml, "data_port", json_integer(source->data_port));
			}
		}
		json_object_set_new(response, "stream", ml);
		/* Also notify event handlers */
		if(notify_events && gateway->events_is_enabled()) {
			json_t *info = json_object();
			json_object_set_new(info, "event", json_string("created"));
			json_object_set_new(info, "id", json_integer(mp->id));
			json_object_set_new(info, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
			gateway->notify_event(&janus_streaming_plugin, session ? session->handle : NULL, info);
		}
		goto prepare_response;
	} else if(!strcasecmp(request_text, "edit")) {
		JANUS_LOG(LOG_VERB, "Attempt to edit an existing streaming mountpoint\n");
		JANUS_VALIDATE_JSON_OBJECT(root, edit_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto prepare_response;
		/* We only allow for a limited set of properties to be edited */
		json_t *id = json_object_get(root, "id");
		json_t *desc = json_object_get(root, "new_description");
		json_t *secret = json_object_get(root, "new_secret");
		json_t *pin = json_object_get(root, "new_pin");
		json_t *is_private = json_object_get(root, "new_is_private");
		json_t *permanent = json_object_get(root, "permanent");
		gboolean save = permanent ? json_is_true(permanent) : FALSE;
		if(save && config == NULL) {
			JANUS_LOG(LOG_ERR, "No configuration file, can't edit mountpoint permanently\n");
			error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
			g_snprintf(error_cause, 512, "No configuration file, can't edit mountpoint permanently");
			goto prepare_response;
		}
		guint64 id_value = json_integer_value(id);
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_ERR, "No such mountpoint (%"SCNu64")\n", mp->id);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint (%"SCNu64")", mp->id);
			goto prepare_response;
		}
		janus_refcount_increase(&mp->ref);
		janus_mutex_lock(&mp->mutex);
		/* A secret may be required for this action */
		JANUS_CHECK_SECRET(mp->secret, root, "secret", error_code, error_cause,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
		if(error_code != 0) {
			janus_mutex_unlock(&mp->mutex);
			janus_mutex_unlock(&mountpoints_mutex);
			janus_refcount_decrease(&mp->ref);
			goto prepare_response;
		}
		/* Edit the mountpoint properties that were provided */
		if(desc != NULL && strlen(json_string_value(desc)) > 0) {
			char *old_description = mp->description;
			char *new_description = g_strdup(json_string_value(desc));
			mp->description = new_description;
			g_free(old_description);
		}
		if(is_private)
			mp->is_private = json_is_true(is_private);
		/* A secret may be required for this action */
		JANUS_CHECK_SECRET(mp->secret, root, "secret", error_code, error_cause,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
		if(error_code != 0) {
			janus_mutex_unlock(&mp->mutex);
			janus_mutex_unlock(&mountpoints_mutex);
			janus_refcount_decrease(&mp->ref);
			goto prepare_response;
		}
		if(secret && strlen(json_string_value(secret)) > 0) {
			char *old_secret = mp->secret;
			char *new_secret = g_strdup(json_string_value(secret));
			mp->secret = new_secret;
			g_free(old_secret);
		}
		if(pin && strlen(json_string_value(pin)) > 0) {
			char *old_pin = mp->pin;
			char *new_pin = g_strdup(json_string_value(pin));
			mp->pin = new_pin;
			g_free(old_pin);
		}
		if(save) {
			JANUS_LOG(LOG_VERB, "Saving editted mountpoint %"SCNu64" permanently in config file\n", mp->id);
			janus_mutex_lock(&config_mutex);
			char value[BUFSIZ];
			/* Remove the old category first */
			janus_config_remove(config, NULL, mp->name);
			/* Now write the room details again */
			janus_config_category *c = janus_config_get_create(config, NULL, janus_config_type_category, mp->name);
			/* Now for the common values at top */
			g_snprintf(value, BUFSIZ, "%"SCNu64, mp->id);
			janus_config_add(config, c, janus_config_item_create("id", value));
			janus_config_add(config, c, janus_config_item_create("description", mp->description));
			if(mp->is_private)
				janus_config_add(config, c, janus_config_item_create("is_private", "yes"));
			/* Per type values */
			if(mp->streaming_source == janus_streaming_source_rtp) {
				gboolean rtsp = FALSE;
#ifdef HAVE_LIBCURL
				janus_streaming_rtp_source *source = mp->source;
				if(source->rtsp)
						rtsp = TRUE;
#endif
				if(rtsp) {
#ifdef HAVE_LIBCURL
					janus_config_add(config, c, janus_config_item_create("type", "rtsp"));
					if(source->rtsp_url)
						janus_config_add(config, c, janus_config_item_create("url", source->rtsp_url));
					if(source->rtsp_username)
						janus_config_add(config, c, janus_config_item_create("rtsp_user", source->rtsp_username));
					if(source->rtsp_password)
						janus_config_add(config, c, janus_config_item_create("rtsp_pwd", source->rtsp_password));
#endif
					janus_config_add(config, c, janus_config_item_create("audio", mp->codecs.audio_pt >= 0 ? "yes" : "no"));
					if(mp->codecs.audio_pt >= 0) {
						if(mp->codecs.audio_rtpmap)
							janus_config_add(config, c, janus_config_item_create("audiortpmap", mp->codecs.audio_rtpmap));
						if(mp->codecs.audio_fmtp)
							janus_config_add(config, c, janus_config_item_create("audiofmtp", mp->codecs.audio_fmtp));
					}
					janus_config_add(config, c, janus_config_item_create("video", mp->codecs.video_pt > 0 ? "yes" : "no"));
					if(mp->codecs.video_pt > 0) {
						if(mp->codecs.video_rtpmap)
							janus_config_add(config, c, janus_config_item_create("videortpmap", mp->codecs.video_rtpmap));
						if(mp->codecs.video_fmtp)
							janus_config_add(config, c, janus_config_item_create("videofmtp", mp->codecs.video_fmtp));
					}
					json_t *iface = json_object_get(root, "rtspiface");
					if(iface)
						janus_config_add(config, c, janus_config_item_create("rtspiface", json_string_value(iface)));
				} else {
					janus_config_add(config, c, janus_config_item_create("type", "rtp"));
					janus_config_add(config, c, janus_config_item_create("audio", mp->codecs.audio_pt >= 0 ? "yes" : "no"));
					janus_streaming_rtp_source *source = mp->source;
					if(mp->codecs.audio_pt >= 0) {
						g_snprintf(value, BUFSIZ, "%d", source->audio_port);
						janus_config_add(config, c, janus_config_item_create("audioport", value));
						if(source->audio_rtcp_port > 0) {
							g_snprintf(value, BUFSIZ, "%d", source->audio_rtcp_port);
							janus_config_add(config, c, janus_config_item_create("audiortcpport", value));
						}
						json_t *audiomcast = json_object_get(root, "audiomcast");
						if(audiomcast)
							janus_config_add(config, c, janus_config_item_create("audiomcast", json_string_value(audiomcast)));
						g_snprintf(value, BUFSIZ, "%d", mp->codecs.audio_pt);
						janus_config_add(config, c, janus_config_item_create("audiopt", value));
						janus_config_add(config, c, janus_config_item_create("audiortpmap", mp->codecs.audio_rtpmap));
						if(mp->codecs.audio_fmtp)
							janus_config_add(config, c, janus_config_item_create("audiofmtp", mp->codecs.audio_fmtp));
						json_t *aiface = json_object_get(root, "audioiface");
						if(aiface)
							janus_config_add(config, c, janus_config_item_create("audioiface", json_string_value(aiface)));
						if(source->askew)
							janus_config_add(config, c, janus_config_item_create("askew", "yes"));
					}
					janus_config_add(config, c, janus_config_item_create("video", mp->codecs.video_pt > 0 ? "yes" : "no"));
					if(mp->codecs.video_pt > 0) {
						g_snprintf(value, BUFSIZ, "%d", source->video_port[0]);
						janus_config_add(config, c, janus_config_item_create("videoport", value));
						if(source->video_rtcp_port > 0) {
							g_snprintf(value, BUFSIZ, "%d", source->video_rtcp_port);
							janus_config_add(config, c, janus_config_item_create("videortcpport", value));
						}
						json_t *videomcast = json_object_get(root, "videomcast");
						if(videomcast)
							janus_config_add(config, c, janus_config_item_create("videomcast", json_string_value(videomcast)));
						g_snprintf(value, BUFSIZ, "%d", mp->codecs.video_pt);
						janus_config_add(config, c, janus_config_item_create("videopt", value));
						janus_config_add(config, c, janus_config_item_create("videortpmap", mp->codecs.video_rtpmap));
						if(mp->codecs.video_fmtp)
							janus_config_add(config, c, janus_config_item_create("videofmtp", mp->codecs.video_fmtp));
						if(source->keyframe.enabled)
							janus_config_add(config, c, janus_config_item_create("videobufferkf", "yes"));
						if(source->simulcast) {
							janus_config_add(config, c, janus_config_item_create("videosimulcast", "yes"));
							if(source->video_port[1]) {
								g_snprintf(value, BUFSIZ, "%d", source->video_port[1]);
								janus_config_add(config, c, janus_config_item_create("videoport2", value));
							}
							if(source->video_port[2]) {
								g_snprintf(value, BUFSIZ, "%d", source->video_port[2]);
								janus_config_add(config, c, janus_config_item_create("videoport3", value));
							}
						}
						if(source->svc)
							janus_config_add(config, c, janus_config_item_create("videosvc", "yes"));
						json_t *viface = json_object_get(root, "videoiface");
						if(viface)
							janus_config_add(config, c, janus_config_item_create("videoiface", json_string_value(viface)));
						if(source->vskew)
							janus_config_add(config, c, janus_config_item_create("videoskew", "yes"));
					}
					if(source->rtp_collision > 0) {
						g_snprintf(value, BUFSIZ, "%d", source->rtp_collision);
						janus_config_add(config, c, janus_config_item_create("collision", value));
					}
					janus_config_add(config, c, janus_config_item_create("data", mp->data ? "yes" : "no"));
					if(source->data_port > -1) {
						g_snprintf(value, BUFSIZ, "%d", source->data_port);
						janus_config_add(config, c, janus_config_item_create("dataport", value));
						if(source->buffermsg)
							janus_config_add(config, c, janus_config_item_create("databuffermsg", "yes"));
						json_t *diface = json_object_get(root, "dataiface");
						if(diface)
							janus_config_add(config, c, janus_config_item_create("dataiface", json_string_value(diface)));
					}
					if(source->srtpsuite > 0 && source->srtpcrypto) {
						g_snprintf(value, BUFSIZ, "%d", source->srtpsuite);
						janus_config_add(config, c, janus_config_item_create("srtpsuite", value));
						janus_config_add(config, c, janus_config_item_create("srtpcrypto", source->srtpcrypto));
					}
					if(mp->helper_threads > 0) {
						g_snprintf(value, BUFSIZ, "%d", mp->helper_threads);
						janus_config_add(config, c, janus_config_item_create("threads", value));
					}
				}
			} else {
				janus_config_add(config, c, janus_config_item_create("type", (mp->streaming_type == janus_streaming_type_live) ? "live" : "ondemand"));
				janus_streaming_file_source *source = mp->source;
				janus_config_add(config, c, janus_config_item_create("filename", source->filename));
				janus_config_add(config, c, janus_config_item_create("audio", mp->codecs.audio_pt >= 0 ? "yes" : "no"));
				janus_config_add(config, c, janus_config_item_create("video", mp->codecs.video_pt > 0 ? "yes" : "no"));
			}
			/* Some more common values */
			if(mp->secret)
				janus_config_add(config, c, janus_config_item_create("secret", mp->secret));
			if(mp->pin)
				janus_config_add(config, c, janus_config_item_create("pin", mp->pin));
			/* Save modified configuration */
			if(janus_config_save(config, config_folder, JANUS_STREAMING_PACKAGE) < 0)
				save = FALSE;	/* This will notify the user the mountpoint is not permanent */
			janus_mutex_unlock(&config_mutex);
		}
		/* Prepare response/notification */
		response = json_object();
		json_object_set_new(response, "streaming", json_string("edited"));
		json_object_set_new(response, "id", json_integer(mp->id));
		json_object_set_new(response, "permanent", save ? json_true() : json_false());
		/* Also notify event handlers */
		if(notify_events && gateway->events_is_enabled()) {
			json_t *info = json_object();
			json_object_set_new(info, "event", json_string("edited"));
			json_object_set_new(info, "id", json_integer(mp->id));
			gateway->notify_event(&janus_streaming_plugin, session ? session->handle : NULL, info);
		}
		janus_mutex_unlock(&mp->mutex);
		janus_mutex_unlock(&mountpoints_mutex);
		janus_refcount_decrease(&mp->ref);
		/* Done */
		JANUS_LOG(LOG_VERB, "Streaming mountpoint edited\n");
		goto prepare_response;
	} else if(!strcasecmp(request_text, "destroy")) {
		/* Get rid of an existing stream (notice this doesn't remove it from the config file, though) */
		JANUS_VALIDATE_JSON_OBJECT(root, destroy_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto prepare_response;
		json_t *id = json_object_get(root, "id");
		json_t *permanent = json_object_get(root, "permanent");
		gboolean save = permanent ? json_is_true(permanent) : FALSE;
		if(save && config == NULL) {
			JANUS_LOG(LOG_ERR, "No configuration file, can't destroy mountpoint permanently\n");
			error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
			g_snprintf(error_cause, 512, "No configuration file, can't destroy mountpoint permanently");
			goto prepare_response;
		}
		guint64 id_value = json_integer_value(id);
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %"SCNu64"", id_value);
			goto prepare_response;
		}
		janus_refcount_increase(&mp->ref);
		/* A secret may be required for this action */
		JANUS_CHECK_SECRET(mp->secret, root, "secret", error_code, error_cause,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
		if(error_code != 0) {
			janus_refcount_decrease(&mp->ref);
			janus_mutex_unlock(&mountpoints_mutex);
			goto prepare_response;
		}
		JANUS_LOG(LOG_VERB, "Request to unmount mountpoint/stream %"SCNu64"\n", id_value);
		/* Remove mountpoint from the hashtable: this will get it destroyed eventually */
		g_hash_table_remove(mountpoints, &id_value);
		/* FIXME Should we kick the current viewers as well? */
		janus_mutex_lock(&mp->mutex);
		GList *viewer = g_list_first(mp->viewers);
		/* Prepare JSON event */
		json_t *event = json_object();
		json_object_set_new(event, "streaming", json_string("event"));
		json_t *result = json_object();
		json_object_set_new(result, "status", json_string("stopped"));
		json_object_set_new(event, "result", result);
		while(viewer) {
			janus_streaming_session *s = (janus_streaming_session *)viewer->data;
			if(s != NULL) {
				s->stopping = TRUE;
				s->started = FALSE;
				s->paused = FALSE;
				s->mountpoint = NULL;
				/* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
				gateway->push_event(s->handle, &janus_streaming_plugin, NULL, event, NULL);
				gateway->close_pc(s->handle);
				janus_refcount_decrease(&s->ref);
				janus_refcount_decrease(&mp->ref);
			}
			if(mp->streaming_source == janus_streaming_source_rtp) {
				/* Remove the viewer from the helper threads too, if any */
				if(mp->helper_threads > 0) {
					GList *l = mp->threads;
					while(l) {
						janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
						janus_mutex_lock(&ht->mutex);
						if(g_list_find(ht->viewers, s) != NULL) {
							ht->num_viewers--;
							ht->viewers = g_list_remove_all(ht->viewers, s);
							janus_mutex_unlock(&ht->mutex);
							JANUS_LOG(LOG_VERB, "Removing viewer from helper thread #%d (destroy)\n", ht->id);
							break;
						}
						janus_mutex_unlock(&ht->mutex);
						l = l->next;
					}
				}
			}
			mp->viewers = g_list_remove_all(mp->viewers, s);
			viewer = g_list_first(mp->viewers);
		}
		json_decref(event);
		janus_mutex_unlock(&mp->mutex);
		if(save) {
			/* This change is permanent: save to the configuration file too
			 * FIXME: We should check if anything fails... */
			JANUS_LOG(LOG_VERB, "Destroying mountpoint %"SCNu64" (%s) permanently in config file\n", mp->id, mp->name);
			janus_mutex_lock(&config_mutex);
			/* The category to remove is the mountpoint name */
			janus_config_remove(config, NULL, mp->name);
			/* Save modified configuration */
			if(janus_config_save(config, config_folder, JANUS_STREAMING_PACKAGE) < 0)
				save = FALSE;	/* This will notify the user the mountpoint is not permanent */
			janus_mutex_unlock(&config_mutex);
		}
		janus_refcount_decrease(&mp->ref);
		/* Also notify event handlers */
		if(notify_events && gateway->events_is_enabled()) {
			json_t *info = json_object();
			json_object_set_new(info, "event", json_string("destroyed"));
			json_object_set_new(info, "id", json_integer(id_value));
			gateway->notify_event(&janus_streaming_plugin, session ? session->handle : NULL, info);
		}
		janus_mutex_unlock(&mountpoints_mutex);
		/* Send info back */
		response = json_object();
		json_object_set_new(response, "streaming", json_string("destroyed"));
		json_object_set_new(response, "destroyed", json_integer(id_value));
		goto prepare_response;
	} else if(!strcasecmp(request_text, "recording")) {
		/* We can start/stop recording a live, RTP-based stream */
		JANUS_VALIDATE_JSON_OBJECT(root, recording_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto prepare_response;
		json_t *action = json_object_get(root, "action");
		const char *action_text = json_string_value(action);
		if(strcasecmp(action_text, "start") && strcasecmp(action_text, "stop")) {
			JANUS_LOG(LOG_ERR, "Invalid action (should be start|stop)\n");
			error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
			g_snprintf(error_cause, 512, "Invalid action (should be start|stop)");
			goto prepare_response;
		}
		json_t *id = json_object_get(root, "id");
		guint64 id_value = json_integer_value(id);
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %"SCNu64"", id_value);
			goto prepare_response;
		}
		janus_refcount_increase(&mp->ref);
		if(mp->streaming_type != janus_streaming_type_live || mp->streaming_source != janus_streaming_source_rtp) {
			janus_refcount_decrease(&mp->ref);
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_ERR, "Recording is only available on RTP-based live streams\n");
			error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
			g_snprintf(error_cause, 512, "Recording is only available on RTP-based live streams");
			goto prepare_response;
		}
		/* A secret may be required for this action */
		JANUS_CHECK_SECRET(mp->secret, root, "secret", error_code, error_cause,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
		if(error_code != 0) {
			janus_refcount_decrease(&mp->ref);
			janus_mutex_unlock(&mountpoints_mutex);
			goto prepare_response;
		}
		janus_streaming_rtp_source *source = mp->source;
		if(!strcasecmp(action_text, "start")) {
			/* Start a recording for audio and/or video */
			JANUS_VALIDATE_JSON_OBJECT(root, recording_start_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0) {
				janus_refcount_decrease(&mp->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *audio = json_object_get(root, "audio");
			json_t *video = json_object_get(root, "video");
			json_t *data = json_object_get(root, "data");
			janus_recorder *arc = NULL, *vrc = NULL, *drc = NULL;
			if((audio && source->arc) || (video && source->vrc) || (data && source->drc)) {
				janus_refcount_decrease(&mp->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				JANUS_LOG(LOG_ERR, "Recording for audio, video and/or data already started for this stream\n");
				error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
				g_snprintf(error_cause, 512, "Recording for audio, video and/or data already started for this stream");
				goto prepare_response;
			}
			if(!audio && !video && !data) {
				janus_refcount_decrease(&mp->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				JANUS_LOG(LOG_ERR, "Missing audio, video and/or data\n");
				error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
				g_snprintf(error_cause, 512, "Missing audio, video and/or data");
				goto prepare_response;
			}
			if(audio) {
				const char *codec = NULL;
				if (!mp->codecs.audio_rtpmap)
					JANUS_LOG(LOG_ERR, "[%s] Audio RTP map is uninitialized\n", mp->name);
				else if(strstr(mp->codecs.audio_rtpmap, "opus") || strstr(mp->codecs.audio_rtpmap, "OPUS"))
					codec = "opus";
				else if(strstr(mp->codecs.audio_rtpmap, "pcma") || strstr(mp->codecs.audio_rtpmap, "PCMA"))
					codec = "pcma";
				else if(strstr(mp->codecs.audio_rtpmap, "pcmu") || strstr(mp->codecs.audio_rtpmap, "PCMU"))
					codec = "pcmu";
				else if(strstr(mp->codecs.audio_rtpmap, "g722") || strstr(mp->codecs.audio_rtpmap, "G722"))
					codec = "g722";
				const char *audiofile = json_string_value(audio);
				arc = janus_recorder_create(NULL, codec, (char *)audiofile);
				if(arc == NULL) {
					JANUS_LOG(LOG_ERR, "[%s] Error starting recorder for audio\n", mp->name);
					janus_refcount_decrease(&mp->ref);
					janus_mutex_unlock(&mountpoints_mutex);
					error_code = JANUS_STREAMING_ERROR_CANT_RECORD;
					g_snprintf(error_cause, 512, "Error starting recorder for audio");
					goto prepare_response;
				}
				JANUS_LOG(LOG_INFO, "[%s] Audio recording started\n", mp->name);
			}
			if(video) {
				const char *codec = NULL;
				if (!mp->codecs.video_rtpmap)
					JANUS_LOG(LOG_ERR, "[%s] Video RTP map is uninitialized\n", mp->name);
				else if(strstr(mp->codecs.video_rtpmap, "vp8") || strstr(mp->codecs.video_rtpmap, "VP8"))
					codec = "vp8";
				else if(strstr(mp->codecs.video_rtpmap, "vp9") || strstr(mp->codecs.video_rtpmap, "VP9"))
					codec = "vp9";
				else if(strstr(mp->codecs.video_rtpmap, "h264") || strstr(mp->codecs.video_rtpmap, "H264"))
					codec = "h264";
				const char *videofile = json_string_value(video);
				vrc = janus_recorder_create(NULL, codec, (char *)videofile);
				if(vrc == NULL) {
					if(arc != NULL) {
						janus_recorder_close(arc);
						janus_recorder_destroy(arc);
						arc = NULL;
					}
					JANUS_LOG(LOG_ERR, "[%s] Error starting recorder for video\n", mp->name);
					janus_refcount_decrease(&mp->ref);
					janus_mutex_unlock(&mountpoints_mutex);
					error_code = JANUS_STREAMING_ERROR_CANT_RECORD;
					g_snprintf(error_cause, 512, "Error starting recorder for video");
					goto prepare_response;
				}
				JANUS_LOG(LOG_INFO, "[%s] Video recording started\n", mp->name);
			}
			if(data) {
				const char *datafile = json_string_value(data);
				drc = janus_recorder_create(NULL, "text", (char *)datafile);
				if(drc == NULL) {
					if(arc != NULL) {
						janus_recorder_close(arc);
						janus_recorder_destroy(arc);
						arc = NULL;
					}
					if(vrc != NULL) {
						janus_recorder_close(vrc);
						janus_recorder_destroy(vrc);
						vrc = NULL;
					}
					JANUS_LOG(LOG_ERR, "[%s] Error starting recorder for data\n", mp->name);
					janus_refcount_decrease(&mp->ref);
					janus_mutex_unlock(&mountpoints_mutex);
					error_code = JANUS_STREAMING_ERROR_CANT_RECORD;
					g_snprintf(error_cause, 512, "Error starting recorder for data");
					goto prepare_response;
				}
				JANUS_LOG(LOG_INFO, "[%s] Data recording started\n", mp->name);
			}
			if(arc != NULL)
				source->arc = arc;
			if(vrc != NULL)
				source->vrc = vrc;
			if(drc != NULL)
				source->drc = drc;
			janus_refcount_decrease(&mp->ref);
			janus_mutex_unlock(&mountpoints_mutex);
			/* Send a success response back */
			response = json_object();
			json_object_set_new(response, "streaming", json_string("ok"));
			goto prepare_response;
		} else if(!strcasecmp(action_text, "stop")) {
			/* Stop the recording */
			JANUS_VALIDATE_JSON_OBJECT(root, recording_stop_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0) {
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *audio = json_object_get(root, "audio");
			json_t *video = json_object_get(root, "video");
			json_t *data = json_object_get(root, "data");
			if(!audio && !video) {
				janus_mutex_unlock(&mountpoints_mutex);
				JANUS_LOG(LOG_ERR, "Missing audio and/or video\n");
				error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
				g_snprintf(error_cause, 512, "Missing audio and/or video");
				goto prepare_response;
			}
			janus_mutex_lock(&source->rec_mutex);
			if(audio && json_is_true(audio) && source->arc) {
				/* Close the audio recording */
				janus_recorder_close(source->arc);
				JANUS_LOG(LOG_INFO, "[%s] Closed audio recording %s\n", mp->name, source->arc->filename ? source->arc->filename : "??");
				janus_recorder *tmp = source->arc;
				source->arc = NULL;
				janus_recorder_destroy(tmp);
			}
			if(video && json_is_true(video) && source->vrc) {
				/* Close the video recording */
				janus_recorder_close(source->vrc);
				JANUS_LOG(LOG_INFO, "[%s] Closed video recording %s\n", mp->name, source->vrc->filename ? source->vrc->filename : "??");
				janus_recorder *tmp = source->vrc;
				source->vrc = NULL;
				janus_recorder_destroy(tmp);
			}
			if(data && json_is_true(data) && source->drc) {
				/* Close the data recording */
				janus_recorder_close(source->drc);
				JANUS_LOG(LOG_INFO, "[%s] Closed data recording %s\n", mp->name, source->drc->filename ? source->drc->filename : "??");
				janus_recorder *tmp = source->drc;
				source->drc = NULL;
				janus_recorder_destroy(tmp);
			}
			janus_mutex_unlock(&source->rec_mutex);
			janus_refcount_decrease(&mp->ref);
			janus_mutex_unlock(&mountpoints_mutex);
			/* Send a success response back */
			response = json_object();
			json_object_set_new(response, "streaming", json_string("ok"));
			goto prepare_response;
		}
	} else if(!strcasecmp(request_text, "enable") || !strcasecmp(request_text, "disable")) {
		/* A request to enable/disable a mountpoint */
		JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto prepare_response;
		json_t *id = json_object_get(root, "id");
		guint64 id_value = json_integer_value(id);
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %"SCNu64"", id_value);
			goto prepare_response;
		}
		janus_refcount_increase(&mp->ref);
		/* A secret may be required for this action */
		JANUS_CHECK_SECRET(mp->secret, root, "secret", error_code, error_cause,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
		if(error_code != 0) {
			janus_refcount_decrease(&mp->ref);
			janus_mutex_unlock(&mountpoints_mutex);
			goto prepare_response;
		}
		if(!strcasecmp(request_text, "enable")) {
			/* Enable a previously disabled mountpoint */
			JANUS_LOG(LOG_INFO, "[%s] Stream enabled\n", mp->name);
			mp->enabled = TRUE;
			/* FIXME: Should we notify the viewers, or is this up to the controller application? */
		} else {
			/* Disable a previously enabled mountpoint */
			JANUS_LOG(LOG_INFO, "[%s] Stream disabled\n", mp->name);
			mp->enabled = FALSE;
			/* Any recording to close? */
			if(mp->streaming_source == janus_streaming_source_rtp) {
				janus_streaming_rtp_source *source = mp->source;
				janus_mutex_lock(&source->rec_mutex);
				if(source->arc) {
					janus_recorder_close(source->arc);
					JANUS_LOG(LOG_INFO, "[%s] Closed audio recording %s\n", mp->name, source->arc->filename ? source->arc->filename : "??");
					janus_recorder *tmp = source->arc;
					source->arc = NULL;
					janus_recorder_destroy(tmp);
				}
				if(source->vrc) {
					janus_recorder_close(source->vrc);
					JANUS_LOG(LOG_INFO, "[%s] Closed video recording %s\n", mp->name, source->vrc->filename ? source->vrc->filename : "??");
					janus_recorder *tmp = source->vrc;
					source->vrc = NULL;
					janus_recorder_destroy(tmp);
				}
				if(source->drc) {
					janus_recorder_close(source->drc);
					JANUS_LOG(LOG_INFO, "[%s] Closed data recording %s\n", mp->name, source->drc->filename ? source->drc->filename : "??");
					janus_recorder *tmp = source->drc;
					source->drc = NULL;
					janus_recorder_destroy(tmp);
				}
				janus_mutex_unlock(&source->rec_mutex);
			}
			/* FIXME: Should we notify the viewers, or is this up to the controller application? */
		}
		janus_refcount_decrease(&mp->ref);
		janus_mutex_unlock(&mountpoints_mutex);
		/* Send a success response back */
		response = json_object();
		json_object_set_new(response, "streaming", json_string("ok"));
		goto prepare_response;
	} else {
		/* Not a request we recognize, don't do anything */
		return NULL;
	}

prepare_response:
		{
			if(ifas) {
				freeifaddrs(ifas);
			}

			if(error_code == 0 && !response) {
				error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "Invalid response");
			}
			if(error_code != 0) {
				/* Prepare JSON error event */
				response = json_object();
				json_object_set_new(response, "streaming", json_string("event"));
				json_object_set_new(response, "error_code", json_integer(error_code));
				json_object_set_new(response, "error", json_string(error_cause));
			}
			return response;
		}

}

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

	/* Pre-parse the message */
	int error_code = 0;
	char error_cause[512];
	json_t *root = message;
	json_t *response = NULL;

	janus_mutex_lock(&sessions_mutex);
	janus_streaming_session *session = janus_streaming_lookup_session(handle);
	if(!session) {
		janus_mutex_unlock(&sessions_mutex);
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
		g_snprintf(error_cause, 512, "%s", "No session associated with this handle...");
		goto plugin_response;
	}
	/* Increase the reference counter for this session: we'll decrease it after we handle the message */
	janus_refcount_increase(&session->ref);
	janus_mutex_unlock(&sessions_mutex);
	if(g_atomic_int_get(&session->destroyed)) {
		JANUS_LOG(LOG_ERR, "Session has already been destroyed...\n");
		error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
		g_snprintf(error_cause, 512, "%s", "Session has already been destroyed...");
		goto plugin_response;
	}

	if(message == NULL) {
		JANUS_LOG(LOG_ERR, "No message??\n");
		error_code = JANUS_STREAMING_ERROR_NO_MESSAGE;
		g_snprintf(error_cause, 512, "%s", "No message??");
		goto plugin_response;
	}
	if(!json_is_object(root)) {
		JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
		error_code = JANUS_STREAMING_ERROR_INVALID_JSON;
		g_snprintf(error_cause, 512, "JSON error: not an object");
		goto plugin_response;
	}
	/* Get the request first */
	JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
		error_code, error_cause, TRUE,
		JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
	if(error_code != 0)
		goto plugin_response;
	json_t *request = json_object_get(root, "request");
	/* Some requests ('create' and 'destroy') can be handled synchronously */
	const char *request_text = json_string_value(request);
	/* We have a separate method to process synchronous requests, as those may
	 * arrive from the Admin API as well, and so we handle them the same way */
	response = janus_streaming_process_synchronous_request(session, root);
	if(response != NULL) {
		/* We got a response, send it back */
		goto plugin_response;
	} else if(!strcasecmp(request_text, "watch") || !strcasecmp(request_text, "start")
			|| !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "stop")
			|| !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "switch")) {
		/* These messages are handled asynchronously */
		janus_streaming_message *msg = g_malloc(sizeof(janus_streaming_message));
		msg->handle = handle;
		msg->transaction = transaction;
		msg->message = root;
		msg->jsep = jsep;

		g_async_queue_push(messages, msg);
		return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
	} else {
		JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
		error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
		g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
	}

plugin_response:
		{
			if(error_code == 0 && !response) {
				error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "Invalid response");
			}
			if(error_code != 0) {
				/* Prepare JSON error event */
				json_t *event = json_object();
				json_object_set_new(event, "streaming", json_string("event"));
				json_object_set_new(event, "error_code", json_integer(error_code));
				json_object_set_new(event, "error", json_string(error_cause));
				response = event;
			}
			if(root != NULL)
				json_decref(root);
			if(jsep != NULL)
				json_decref(jsep);
			g_free(transaction);

			if(session != NULL)
				janus_refcount_decrease(&session->ref);
			return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
		}

}

json_t *janus_streaming_handle_admin_message(json_t *message) {
	/* Some requests (e.g., 'create' and 'destroy') can be handled via Admin API */
	int error_code = 0;
	char error_cause[512];
	json_t *response = NULL;

	JANUS_VALIDATE_JSON_OBJECT(message, request_parameters,
		error_code, error_cause, TRUE,
		JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
	if(error_code != 0)
		goto admin_response;
	json_t *request = json_object_get(message, "request");
	const char *request_text = json_string_value(request);
	if((response = janus_streaming_process_synchronous_request(NULL, message)) != NULL) {
		/* We got a response, send it back */
		goto admin_response;
	} else {
		JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
		error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
		g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
	}

admin_response:
		{
			if(!response) {
				/* Prepare JSON error event */
				response = json_object();
				json_object_set_new(response, "streaming", json_string("event"));
				json_object_set_new(response, "error_code", json_integer(error_code));
				json_object_set_new(response, "error", json_string(error_cause));
			}
			return response;
		}

}

void janus_streaming_setup_media(janus_plugin_session *handle) {
	JANUS_LOG(LOG_INFO, "[%s-%p] WebRTC media is now available\n", JANUS_STREAMING_PACKAGE, handle);
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	janus_mutex_lock(&sessions_mutex);
	janus_streaming_session *session = janus_streaming_lookup_session(handle);
	if(!session) {
		janus_mutex_unlock(&sessions_mutex);
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		return;
	}
	if(g_atomic_int_get(&session->destroyed)) {
		janus_mutex_unlock(&sessions_mutex);
		return;
	}
	janus_refcount_increase(&session->ref);
	janus_mutex_unlock(&sessions_mutex);
	g_atomic_int_set(&session->hangingup, 0);
	/* We only start streaming towards this user when we get this event */
	janus_rtp_switching_context_reset(&session->context);
	/* If this is related to a live RTP mountpoint, any keyframe we can shoot already? */
	janus_streaming_mountpoint *mountpoint = session->mountpoint;
	if(mountpoint->streaming_source == janus_streaming_source_rtp) {
		janus_streaming_rtp_source *source = mountpoint->source;
		if(source->keyframe.enabled) {
			JANUS_LOG(LOG_HUGE, "Any keyframe to send?\n");
			janus_mutex_lock(&source->keyframe.mutex);
			if(source->keyframe.latest_keyframe != NULL) {
				JANUS_LOG(LOG_HUGE, "Yep! %d packets\n", g_list_length(source->keyframe.latest_keyframe));
				GList *temp = source->keyframe.latest_keyframe;
				while(temp) {
					janus_streaming_relay_rtp_packet(session, temp->data);
					temp = temp->next;
				}
			}
			janus_mutex_unlock(&source->keyframe.mutex);
		}
		if(source->buffermsg) {
			JANUS_LOG(LOG_HUGE, "Any recent datachannel message to send?\n");
			janus_mutex_lock(&source->buffermsg_mutex);
			if(source->last_msg != NULL) {
				JANUS_LOG(LOG_HUGE, "Yep!\n");
				janus_streaming_relay_rtp_packet(session, source->last_msg);
			}
			janus_mutex_unlock(&source->buffermsg_mutex);
		}
	}
	session->started = TRUE;
	/* Prepare JSON event */
	json_t *event = json_object();
	json_object_set_new(event, "streaming", json_string("event"));
	json_t *result = json_object();
	json_object_set_new(result, "status", json_string("started"));
	json_object_set_new(event, "result", result);
	int ret = gateway->push_event(handle, &janus_streaming_plugin, NULL, event, NULL);
	JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
	json_decref(event);
	janus_refcount_decrease(&session->ref);
}

void janus_streaming_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
	if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	/* FIXME We don't care about what the browser sends us, we're sendonly */
}

void janus_streaming_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
	if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	janus_streaming_session *session = (janus_streaming_session *)handle->plugin_handle;
	if(!session || g_atomic_int_get(&session->destroyed) || session->stopping || !session->started || session->paused)
		return;
	janus_streaming_mountpoint *mp = (janus_streaming_mountpoint *)session->mountpoint;
	if(mp->streaming_source != janus_streaming_source_rtp)
		return;
	janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)mp->source;
	if(!video && (source->audio_rtcp_fd > -1) && (source->audio_rtcp_addr.sa_family != 0)) {
		JANUS_LOG(LOG_HUGE, "Got audio RTCP feedback from a viewer: SSRC %"SCNu32"\n",
			janus_rtcp_get_sender_ssrc(buf, len));
		/* FIXME We don't forward RR packets, so what should we check here? */
	} else if(video && (source->video_rtcp_fd > -1) && (source->video_rtcp_addr.sa_family != 0)) {
		JANUS_LOG(LOG_HUGE, "Got video RTCP feedback from a viewer: SSRC %"SCNu32"\n",
			janus_rtcp_get_sender_ssrc(buf, len));
		/* We only relay PLI/FIR and REMB packets, but in a selective way */
		if(janus_rtcp_has_fir(buf, len) || janus_rtcp_has_pli(buf, len)) {
			/* We got a PLI/FIR, pass it along unless we just sent one */
			JANUS_LOG(LOG_HUGE, "  -- Keyframe request\n");
			janus_streaming_rtcp_pli_send(source);
		}
		uint64_t bw = janus_rtcp_get_remb(buf, len);
		if(bw > 0) {
			/* Keep track of this value, if this is the lowest right now */
			JANUS_LOG(LOG_HUGE, "  -- REMB for this PeerConnection: %"SCNu64"\n", bw);
			if((0 == source->lowest_bitrate) || (source->lowest_bitrate > bw))
				source->lowest_bitrate = bw;
		}
	}
}

void janus_streaming_hangup_media(janus_plugin_session *handle) {
	JANUS_LOG(LOG_INFO, "[%s-%p] No WebRTC media anymore\n", JANUS_STREAMING_PACKAGE, handle);
	janus_mutex_lock(&sessions_mutex);
	janus_streaming_hangup_media_internal(handle);
	janus_mutex_unlock(&sessions_mutex);
}

static void janus_streaming_hangup_media_internal(janus_plugin_session *handle) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	janus_streaming_session *session = janus_streaming_lookup_session(handle);
	if(!session) {
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		return;
	}
	if(g_atomic_int_get(&session->destroyed))
		return;
	if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1))
		return;
	janus_rtp_switching_context_reset(&session->context);
	janus_rtp_simulcasting_context_reset(&session->sim_context);
	janus_vp8_simulcast_context_reset(&session->vp8_context);
	session->spatial_layer = -1;
	session->target_spatial_layer = 1;	/* FIXME Chrome sends 0 and 1 */
	session->temporal_layer = -1;
	session->target_temporal_layer = 2;	/* FIXME Chrome sends 0, 1 and 2 */
	session->stopping = TRUE;
	session->started = FALSE;
	session->paused = FALSE;
	janus_streaming_mountpoint *mp = session->mountpoint;
	if(mp) {
		janus_mutex_lock(&mp->mutex);
		JANUS_LOG(LOG_VERB, "  -- Removing the session from the mountpoint viewers\n");
		if(g_list_find(mp->viewers, session) != NULL) {
			JANUS_LOG(LOG_VERB, "  -- -- Found!\n");
			janus_refcount_decrease(&mp->ref);
			janus_refcount_decrease(&session->ref);
		}
		mp->viewers = g_list_remove_all(mp->viewers, session);
		if(mp->streaming_source == janus_streaming_source_rtp) {
			/* Remove the viewer from the helper threads too, if any */
			if(mp->helper_threads > 0) {
				GList *l = mp->threads;
				while(l) {
					janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
					janus_mutex_lock(&ht->mutex);
					if(g_list_find(ht->viewers, session) != NULL) {
						ht->num_viewers--;
						ht->viewers = g_list_remove_all(ht->viewers, session);
						janus_mutex_unlock(&ht->mutex);
						JANUS_LOG(LOG_VERB, "Removing viewer from helper thread #%d\n", ht->id);
						break;
					}
					janus_mutex_unlock(&ht->mutex);
					l = l->next;
				}
			}
		}
		janus_mutex_unlock(&mp->mutex);
	}
	session->mountpoint = NULL;
	g_atomic_int_set(&session->hangingup, 0);
}

/* Thread to handle incoming messages */
static void *janus_streaming_handler(void *data) {
	JANUS_LOG(LOG_VERB, "Joining Streaming handler thread\n");
	janus_streaming_message *msg = NULL;
	int error_code = 0;
	char error_cause[512];
	json_t *root = NULL;
	while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
		msg = g_async_queue_pop(messages);
		if(msg == &exit_message)
			break;
		if(msg->handle == NULL) {
			janus_streaming_message_free(msg);
			continue;
		}
		janus_mutex_lock(&sessions_mutex);
		janus_streaming_session *session = janus_streaming_lookup_session(msg->handle);
		if(!session) {
			janus_mutex_unlock(&sessions_mutex);
			JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
			janus_streaming_message_free(msg);
			continue;
		}
		if(g_atomic_int_get(&session->destroyed)) {
			janus_mutex_unlock(&sessions_mutex);
			janus_streaming_message_free(msg);
			continue;
		}
		janus_mutex_unlock(&sessions_mutex);
		/* Handle request */
		error_code = 0;
		root = NULL;
		if(msg->message == NULL) {
			JANUS_LOG(LOG_ERR, "No message??\n");
			error_code = JANUS_STREAMING_ERROR_NO_MESSAGE;
			g_snprintf(error_cause, 512, "%s", "No message??");
			goto error;
		}
		root = msg->message;
		/* Get the request first */
		JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
			error_code, error_cause, TRUE,
			JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto error;
		json_t *request = json_object_get(root, "request");
		const char *request_text = json_string_value(request);
		json_t *result = NULL;
		const char *sdp_type = NULL;
		char *sdp = NULL;
		gboolean do_restart = FALSE;
		/* All these requests can only be handled asynchronously */
		if(!strcasecmp(request_text, "watch")) {
			JANUS_VALIDATE_JSON_OBJECT(root, watch_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto error;
			json_t *id = json_object_get(root, "id");
			json_t *offer_audio = json_object_get(root, "offer_audio");
			json_t *offer_video = json_object_get(root, "offer_video");
			json_t *offer_data = json_object_get(root, "offer_data");
			guint64 id_value = json_integer_value(id);
			json_t *restart = json_object_get(root, "restart");
			do_restart = restart ? json_is_true(restart) : FALSE;
			janus_mutex_lock(&mountpoints_mutex);
			janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
			if(mp == NULL) {
				janus_mutex_unlock(&mountpoints_mutex);
				JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value);
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "No such mountpoint/stream %"SCNu64"", id_value);
				goto error;
			}
			janus_refcount_increase(&mp->ref);
			/* A secret may be required for this action */
			JANUS_CHECK_SECRET(mp->pin, root, "pin", error_code, error_cause,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT, JANUS_STREAMING_ERROR_UNAUTHORIZED);
			if(error_code != 0) {
				janus_refcount_decrease(&mp->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				goto error;
			}
			janus_mutex_lock(&mp->mutex);
			janus_mutex_unlock(&mountpoints_mutex);
			/* Check if this is a new viewer, or if an update is taking place (i.e., ICE restart) */
			if(do_restart) {
				/* User asked for an ICE restart: provide a new offer */
				JANUS_LOG(LOG_VERB, "Request to perform an ICE restart on mountpoint/stream %"SCNu64" subscription\n", id_value);
				session->sdp_version++;	/* This needs to be increased when it changes */
				goto done;
			}
			if(session->mountpoint != NULL) {
				if(session->mountpoint != mp) {
					/* Already watching something else */
					janus_mutex_unlock(&mp->mutex);
					JANUS_LOG(LOG_ERR, "Already watching mountpoint %"SCNu64"\n", session->mountpoint->id);
					error_code = JANUS_STREAMING_ERROR_CANT_SWITCH;
					g_snprintf(error_cause, 512, "Already watching mountpoint %"SCNu64, session->mountpoint->id);
					goto error;
				} else {
					/* Simple renegotiation */
					JANUS_LOG(LOG_VERB, "Request to update mountpoint/stream %"SCNu64" subscription (no restart)\n", id_value);
					session->sdp_version++;	/* This needs to be increased when it changes */
					goto done;
				}
			}
			/* New viewer: we send an offer ourselves */
			JANUS_LOG(LOG_VERB, "Request to watch mountpoint/stream %"SCNu64"\n", id_value);
			if(session->mountpoint != NULL || g_list_find(mp->viewers, session) != NULL) {
				janus_mutex_unlock(&mp->mutex);
				janus_refcount_decrease(&mp->ref);
				JANUS_LOG(LOG_ERR, "Already watching a stream...\n");
				error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "Already watching a stream");
				goto error;
			}
			session->stopping = FALSE;
			session->mountpoint = mp;
			session->sdp_version = 1;	/* This needs to be increased when it changes */
			session->sdp_sessid = janus_get_real_time();
			/* Check what we should offer */
			session->audio = offer_audio ? json_is_true(offer_audio) : TRUE;	/* True by default */
			if(!mp->audio)
				session->audio = FALSE;	/* ... unless the mountpoint isn't sending any audio */
			session->video = offer_video ? json_is_true(offer_video) : TRUE;	/* True by default */
			if(!mp->video)
				session->video = FALSE;	/* ... unless the mountpoint isn't sending any video */
			session->data = offer_data ? json_is_true(offer_data) : TRUE;	/* True by default */
			if(!mp->data)
				session->data = FALSE;	/* ... unless the mountpoint isn't sending any data */
			if((!mp->audio || !session->audio) &&
					(!mp->video || !session->video) &&
					(!mp->data || !session->data)) {
				session->mountpoint = NULL;
				janus_mutex_unlock(&mp->mutex);
				janus_refcount_decrease(&mp->ref);
				JANUS_LOG(LOG_ERR, "Can't offer an SDP with no audio, video or data for this mountpoint\n");
				error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
				g_snprintf(error_cause, 512, "Can't offer an SDP with no audio, video or data for this mountpoint");
				goto error;
			}
			if(mp->streaming_type == janus_streaming_type_on_demand) {
				GError *error = NULL;
				char tname[16];
				g_snprintf(tname, sizeof(tname), "mp %"SCNu64, id_value);
				janus_refcount_increase(&session->ref);
				janus_refcount_increase(&mp->ref);
				g_thread_try_new(tname, &janus_streaming_ondemand_thread, session, &error);
				if(error != NULL) {
					session->mountpoint = NULL;
					janus_mutex_unlock(&mp->mutex);
					janus_refcount_decrease(&session->ref);	/* This is for the failed thread */
					janus_refcount_decrease(&mp->ref);		/* This is for the failed thread */
					janus_refcount_decrease(&mp->ref);
					JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the on-demand thread...\n", error->code, error->message ? error->message : "??");
					error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
					g_snprintf(error_cause, 512, "Got error %d (%s) trying to launch the on-demand thread", error->code, error->message ? error->message : "??");
					goto error;
				}
			} else if(mp->streaming_source == janus_streaming_source_rtp) {
				janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)mp->source;
				if(source && source->simulcast) {
					JANUS_VALIDATE_JSON_OBJECT(root, simulcast_parameters,
						error_code, error_cause, TRUE,
						JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
					if(error_code != 0) {
						session->mountpoint = NULL;
						janus_mutex_unlock(&mp->mutex);
						janus_refcount_decrease(&mp->ref);
						goto error;
					}
					/* In case this mountpoint is simulcasting, let's aim high by default */
					janus_rtp_switching_context_reset(&session->context);
					janus_rtp_simulcasting_context_reset(&session->sim_context);
					session->sim_context.substream_target = 2;
					session->sim_context.templayer_target = 2;
					janus_vp8_simulcast_context_reset(&session->vp8_context);
					/* Unless the request contains a target for either layer */
					json_t *substream = json_object_get(root, "substream");
					if(substream) {
						session->sim_context.substream_target = json_integer_value(substream);
						JANUS_LOG(LOG_VERB, "Setting video substream to let through (simulcast): %d (was %d)\n",
							session->sim_context.substream_target, session->sim_context.substream);
					}
					json_t *temporal = json_object_get(root, "temporal");
					if(temporal) {
						session->sim_context.templayer_target = json_integer_value(temporal);
						JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n",
							session->sim_context.templayer_target, session->sim_context.templayer);
					}
				} else if(source && source->svc) {
					JANUS_VALIDATE_JSON_OBJECT(root, svc_parameters,
						error_code, error_cause, TRUE,
						JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
					if(error_code != 0) {
						session->mountpoint = NULL;
						janus_mutex_unlock(&mp->mutex);
						janus_refcount_decrease(&mp->ref);
						goto error;
					}
					/* In case this mountpoint is doing VP9-SVC, let's aim high by default */
					session->spatial_layer = -1;
					session->target_spatial_layer = 1;	/* FIXME Chrome sends 0 and 1 */
					session->temporal_layer = -1;
					session->target_temporal_layer = 2;	/* FIXME Chrome sends 0, 1 and 2 */
					/* Unless the request contains a target for either layer */
					json_t *spatial = json_object_get(root, "spatial_layer");
					if(spatial) {
						session->target_spatial_layer = json_integer_value(spatial);
						JANUS_LOG(LOG_VERB, "Setting video spatial layer to let through (SVC): %d (was %d)\n",
							session->target_spatial_layer, session->spatial_layer);
					}
					json_t *temporal = json_object_get(root, "temporal_layer");
					if(temporal) {
						session->target_temporal_layer = json_integer_value(temporal);
						JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (SVC): %d (was %d)\n",
							session->target_temporal_layer, session->temporal_layer);
					}
				}
			}
			janus_refcount_increase(&session->ref);
done:
			/* Let's prepare an offer now, but let's also check if there's something we need to skip */
			sdp_type = "offer";	/* We're always going to do the offer ourselves, never answer */
			char sdptemp[2048];
			memset(sdptemp, 0, 2048);
			gchar buffer[512];
			memset(buffer, 0, 512);
			g_snprintf(buffer, 512,
				"v=0\r\no=%s %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n",
					"-", session->sdp_sessid, session->sdp_version);
			g_strlcat(sdptemp, buffer, 2048);
			g_snprintf(buffer, 512,
				"s=Mountpoint %"SCNu64"\r\n", mp->id);
			g_strlcat(sdptemp, buffer, 2048);
			g_strlcat(sdptemp, "t=0 0\r\n", 2048);
			if(mp->codecs.audio_pt >= 0 && session->audio) {
				/* Add audio line */
				g_snprintf(buffer, 512,
					"m=audio 1 RTP/SAVPF %d\r\n"
					"c=IN IP4 1.1.1.1\r\n",
					mp->codecs.audio_pt);
				g_strlcat(sdptemp, buffer, 2048);
				if(mp->codecs.audio_rtpmap) {
					g_snprintf(buffer, 512,
						"a=rtpmap:%d %s\r\n",
						mp->codecs.audio_pt, mp->codecs.audio_rtpmap);
					g_strlcat(sdptemp, buffer, 2048);
				}
				if(mp->codecs.audio_fmtp) {
					g_snprintf(buffer, 512,
						"a=fmtp:%d %s\r\n",
						mp->codecs.audio_pt, mp->codecs.audio_fmtp);
					g_strlcat(sdptemp, buffer, 2048);
				}
				g_strlcat(sdptemp, "a=sendonly\r\n", 2048);
			}
			if(mp->codecs.video_pt > 0 && session->video) {
				/* Add video line */
				g_snprintf(buffer, 512,
					"m=video 1 RTP/SAVPF %d\r\n"
					"c=IN IP4 1.1.1.1\r\n",
					mp->codecs.video_pt);
				g_strlcat(sdptemp, buffer, 2048);
				if(mp->codecs.video_rtpmap) {
					g_snprintf(buffer, 512,
						"a=rtpmap:%d %s\r\n",
						mp->codecs.video_pt, mp->codecs.video_rtpmap);
					g_strlcat(sdptemp, buffer, 2048);
				}
				if(mp->codecs.video_fmtp) {
					g_snprintf(buffer, 512,
						"a=fmtp:%d %s\r\n",
						mp->codecs.video_pt, mp->codecs.video_fmtp);
					g_strlcat(sdptemp, buffer, 2048);
				}
				g_snprintf(buffer, 512,
					"a=rtcp-fb:%d nack\r\n",
					mp->codecs.video_pt);
				g_strlcat(sdptemp, buffer, 2048);
				g_snprintf(buffer, 512,
					"a=rtcp-fb:%d nack pli\r\n",
					mp->codecs.video_pt);
				g_strlcat(sdptemp, buffer, 2048);
				g_snprintf(buffer, 512,
					"a=rtcp-fb:%d goog-remb\r\n",
					mp->codecs.video_pt);
				g_strlcat(sdptemp, buffer, 2048);
				g_strlcat(sdptemp, "a=sendonly\r\n", 2048);
			}
#ifdef HAVE_SCTP
			if(mp->data && session->data) {
				/* Add data line */
				g_snprintf(buffer, 512,
					"m=application 1 DTLS/SCTP 5000\r\n"
					"c=IN IP4 1.1.1.1\r\n"
					"a=sctpmap:5000 webrtc-datachannel 16\r\n");
				g_strlcat(sdptemp, buffer, 2048);
			}
#endif
			sdp = g_strdup(sdptemp);
			JANUS_LOG(LOG_VERB, "Going to %s this SDP:\n%s\n", sdp_type, sdp);
			result = json_object();
			json_object_set_new(result, "status", json_string(do_restart ? "updating" : "preparing"));
			/* Add the user to the list of watchers and we're done */
			mp->viewers = g_list_append(mp->viewers, session);
			if(mp->streaming_source == janus_streaming_source_rtp) {
				/* If we're using helper threads, add the viewer to one of those */
				if(mp->helper_threads > 0) {
					int viewers = -1;
					janus_streaming_helper *helper = NULL;
					GList *l = mp->threads;
					while(l) {
						janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
						if(viewers == -1 || (helper == NULL && ht->num_viewers == 0) || ht->num_viewers < viewers) {
							viewers = ht->num_viewers;
							helper = ht;
						}
						l = l->next;
					}
					janus_mutex_lock(&helper->mutex);
					helper->viewers = g_list_append(helper->viewers, session);
					helper->num_viewers++;
					janus_mutex_unlock(&helper->mutex);
					JANUS_LOG(LOG_VERB, "Added viewer to helper thread #%d (%d viewers)\n",
						helper->id, helper->num_viewers);
				}
			}
			janus_mutex_unlock(&mp->mutex);
		} else if(!strcasecmp(request_text, "start")) {
			if(session->mountpoint == NULL) {
				JANUS_LOG(LOG_VERB, "Can't start: no mountpoint set\n");
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "Can't start: no mountpoint set");
				goto error;
			}
			JANUS_LOG(LOG_VERB, "Starting the streaming\n");
			session->paused = FALSE;
			result = json_object();
			/* We wait for the setup_media event to start: on the other hand, it may have already arrived */
			json_object_set_new(result, "status", json_string(session->started ? "started" : "starting"));
			/* Also notify event handlers */
			if(notify_events && gateway->events_is_enabled()) {
				json_t *info = json_object();
				json_object_set_new(info, "status", json_string("starting"));
				if(session->mountpoint != NULL)
					json_object_set_new(info, "id", json_integer(session->mountpoint->id));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
		} else if(!strcasecmp(request_text, "pause")) {
			if(session->mountpoint == NULL) {
				JANUS_LOG(LOG_VERB, "Can't pause: no mountpoint set\n");
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "Can't start: no mountpoint set");
				goto error;
			}
			JANUS_LOG(LOG_VERB, "Pausing the streaming\n");
			session->paused = TRUE;
			result = json_object();
			json_object_set_new(result, "status", json_string("pausing"));
			/* Also notify event handlers */
			if(notify_events && gateway->events_is_enabled()) {
				json_t *info = json_object();
				json_object_set_new(info, "status", json_string("pausing"));
				if(session->mountpoint != NULL)
					json_object_set_new(info, "id", json_integer(session->mountpoint->id));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
		} else if(!strcasecmp(request_text, "configure")) {
			janus_streaming_mountpoint *mp = session->mountpoint;
			if(mp == NULL) {
				JANUS_LOG(LOG_VERB, "Can't configure: not on a mountpoint\n");
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "Can't configure: not on a mountpoint");
				goto error;
			}
			JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			json_t *audio = json_object_get(root, "audio");
			if(audio)
				session->audio = json_is_true(audio);
			json_t *video = json_object_get(root, "video");
			if(video)
				session->video = json_is_true(video);
			json_t *data = json_object_get(root, "data");
			if(data)
				session->data = json_is_true(data);
			if(mp->streaming_source == janus_streaming_source_rtp) {
				janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)mp->source;
				if(source && source->simulcast) {
					/* Check if the viewer is requesting a different substream/temporal layer */
					json_t *substream = json_object_get(root, "substream");
					if(substream) {
						session->sim_context.substream_target = json_integer_value(substream);
						JANUS_LOG(LOG_VERB, "Setting video substream to let through (simulcast): %d (was %d)\n",
							session->sim_context.substream_target, session->sim_context.substream);
						if(session->sim_context.substream_target == session->sim_context.substream) {
							/* No need to do anything, we're already getting the right substream, so notify the viewer */
							json_t *event = json_object();
							json_object_set_new(event, "streaming", json_string("event"));
							json_t *result = json_object();
							json_object_set_new(result, "substream", json_integer(session->sim_context.substream));
							json_object_set_new(event, "result", result);
							gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
							json_decref(event);
						} else {
							/* Schedule a PLI */
							JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n");
							g_atomic_int_set(&source->need_pli, 1);
						}
					}
					json_t *temporal = json_object_get(root, "temporal");
					if(temporal) {
						session->sim_context.templayer_target = json_integer_value(temporal);
						JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n",
							session->sim_context.templayer_target, session->sim_context.templayer);
						if(mp->codecs.video_codec == JANUS_VIDEOCODEC_VP8 && session->sim_context.templayer_target == session->sim_context.templayer) {
							/* No need to do anything, we're already getting the right temporal layer, so notify the viewer */
							json_t *event = json_object();
							json_object_set_new(event, "streaming", json_string("event"));
							json_t *result = json_object();
							json_object_set_new(result, "temporal", json_integer(session->sim_context.templayer));
							json_object_set_new(event, "result", result);
							gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
							json_decref(event);
						}
					}
				}
				if(source && source->svc) {
					/* Check if the viewer is requesting a different SVC spatial/temporal layer */
					json_t *spatial = json_object_get(root, "spatial_layer");
					if(spatial) {
						int spatial_layer = json_integer_value(spatial);
						if(spatial_layer > 1) {
							JANUS_LOG(LOG_WARN, "Spatial layer higher than 1, will probably be ignored\n");
						}
						if(spatial_layer == session->spatial_layer) {
							/* No need to do anything, we're already getting the right spatial layer, so notify the user */
							json_t *event = json_object();
							json_object_set_new(event, "streaming", json_string("event"));
							json_t *result = json_object();
							json_object_set_new(result, "spatial_layer", json_integer(session->spatial_layer));
							json_object_set_new(event, "result", result);
							gateway->push_event(msg->handle, &janus_streaming_plugin, NULL, event, NULL);
							json_decref(event);
						} else if(spatial_layer != session->target_spatial_layer) {
							/* Send a FIR to the source, if RTCP is enabled */
							g_atomic_int_set(&source->need_pli, 1);
						}
						session->target_spatial_layer = spatial_layer;
					}
					json_t *temporal = json_object_get(root, "temporal_layer");
					if(temporal) {
						int temporal_layer = json_integer_value(temporal);
						if(temporal_layer > 2) {
							JANUS_LOG(LOG_WARN, "Temporal layer higher than 2, will probably be ignored\n");
						}
						if(temporal_layer == session->temporal_layer) {
							/* No need to do anything, we're already getting the right temporal layer, so notify the user */
							json_t *event = json_object();
							json_object_set_new(event, "streaming", json_string("event"));
							json_t *result = json_object();
							json_object_set_new(result, "temporal_layer", json_integer(session->temporal_layer));
							json_object_set_new(event, "result", result);
							gateway->push_event(msg->handle, &janus_streaming_plugin, NULL, event, NULL);
							json_decref(event);
						}
						session->target_temporal_layer = temporal_layer;
					}
				}
			}
			/* Done */
			result = json_object();
			json_object_set_new(result, "event", json_string("configured"));
		} else if(!strcasecmp(request_text, "switch")) {
			/* This listener wants to switch to a different mountpoint
			 * NOTE: this only works for live RTP streams as of now: you
			 * cannot, for instance, switch from a live RTP mountpoint to
			 * an on demand one or viceversa (TBD.) */
			janus_streaming_mountpoint *oldmp = session->mountpoint;
			if(oldmp == NULL) {
				JANUS_LOG(LOG_VERB, "Can't switch: not on a mountpoint\n");
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "Can't switch: not on a mountpoint");
				goto error;
			}
			if(oldmp->streaming_type != janus_streaming_type_live ||
					oldmp->streaming_source != janus_streaming_source_rtp) {
				JANUS_LOG(LOG_VERB, "Can't switch: not on a live RTP mountpoint\n");
				error_code = JANUS_STREAMING_ERROR_CANT_SWITCH;
				g_snprintf(error_cause, 512, "Can't switch: not on a live RTP mountpoint");
				goto error;
			}
			janus_refcount_increase(&oldmp->ref);
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			if(error_code != 0) {
				janus_refcount_decrease(&oldmp->ref);
				goto error;
			}
			json_t *id = json_object_get(root, "id");
			guint64 id_value = json_integer_value(id);
			janus_mutex_lock(&mountpoints_mutex);
			janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, &id_value);
			if(mp == NULL) {
				janus_mutex_unlock(&mountpoints_mutex);
				JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value);
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "No such mountpoint/stream %"SCNu64"", id_value);
				goto error;
			}
			janus_refcount_increase(&mp->ref);	/* If the switch succeeds, we don't decrease this now */
			if(mp->streaming_type != janus_streaming_type_live ||
					mp->streaming_source != janus_streaming_source_rtp) {
				janus_refcount_decrease(&oldmp->ref);
				janus_refcount_decrease(&mp->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				JANUS_LOG(LOG_VERB, "Can't switch: target is not a live RTP mountpoint\n");
				error_code = JANUS_STREAMING_ERROR_CANT_SWITCH;
				g_snprintf(error_cause, 512, "Can't switch: target is not a live RTP mountpoint");
				goto error;
			}
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "Request to switch to mountpoint/stream %"SCNu64" (old: %"SCNu64")\n", id_value, oldmp->id);
			session->paused = TRUE;
			/* Unsubscribe from the previous mountpoint and subscribe to the new one */
			janus_mutex_lock(&oldmp->mutex);
			oldmp->viewers = g_list_remove_all(oldmp->viewers, session);
			/* Remove the viewer from the helper threads too, if any */
			if(oldmp->helper_threads > 0) {
				GList *l = oldmp->threads;
				while(l) {
					janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
					janus_mutex_lock(&ht->mutex);
					if(g_list_find(ht->viewers, session) != NULL) {
						ht->num_viewers--;
						ht->viewers = g_list_remove_all(ht->viewers, session);
						janus_mutex_unlock(&ht->mutex);
						JANUS_LOG(LOG_VERB, "Removing viewer from helper thread #%d (switching)\n", ht->id);
						break;
					}
					janus_mutex_unlock(&ht->mutex);
					l = l->next;
				}
			}
			janus_refcount_decrease(&oldmp->ref);	/* This is for the user going away */
			janus_mutex_unlock(&oldmp->mutex);
			/* Subscribe to the new one */
			janus_mutex_lock(&mp->mutex);
			mp->viewers = g_list_append(mp->viewers, session);
			/* If we're using helper threads, add the viewer to one of those */
			if(mp->helper_threads > 0) {
				int viewers = 0;
				janus_streaming_helper *helper = NULL;
				GList *l = mp->threads;
				while(l) {
					janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
					if(ht->num_viewers == 0 || ht->num_viewers < viewers) {
						viewers = ht->num_viewers;
						helper = ht;
					}
					l = l->next;
				}
				JANUS_LOG(LOG_VERB, "Adding viewer to helper thread #%d\n", helper->id);
				janus_mutex_lock(&helper->mutex);
				helper->viewers = g_list_append(helper->viewers, session);
				helper->num_viewers++;
				janus_mutex_unlock(&helper->mutex);
			}
			janus_mutex_unlock(&mp->mutex);
			session->mountpoint = mp;
			session->paused = FALSE;
			/* Done */
			janus_refcount_decrease(&oldmp->ref);	/* This is for the request being done with it */
			result = json_object();
			json_object_set_new(result, "switched", json_string("ok"));
			json_object_set_new(result, "id", json_integer(id_value));
			/* Also notify event handlers */
			if(notify_events && gateway->events_is_enabled()) {
				json_t *info = json_object();
				json_object_set_new(info, "status", json_string("switching"));
				json_object_set_new(info, "id", json_integer(id_value));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
		} else if(!strcasecmp(request_text, "stop")) {
			if(session->stopping || !session->started) {
				/* Been there, done that: ignore */
				janus_streaming_message_free(msg);
				continue;
			}
			JANUS_LOG(LOG_VERB, "Stopping the streaming\n");
			result = json_object();
			json_object_set_new(result, "status", json_string("stopping"));
			/* Also notify event handlers */
			if(notify_events && gateway->events_is_enabled()) {
				json_t *info = json_object();
				json_object_set_new(info, "status", json_string("stopping"));
				janus_streaming_mountpoint *mp = session->mountpoint;
				if(mp)
					json_object_set_new(info, "id", json_integer(mp->id));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
			/* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
			gateway->close_pc(session->handle);
		} else {
			JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
			error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
			g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
			goto error;
		}

		/* Any SDP to handle? */
		const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
		const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
		if(msg_sdp) {
			JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well (%s):\n%s\n",
				do_restart ? "renegotiation occurring" : "but we really don't care", msg_sdp_type, msg_sdp);
		}

		/* Prepare JSON event */
		json_t *jsep = json_pack("{ssss}", "type", sdp_type, "sdp", sdp);
		if(do_restart)
			json_object_set_new(jsep, "restart", json_true());
		json_t *event = json_object();
		json_object_set_new(event, "streaming", json_string("event"));
		if(result != NULL)
			json_object_set_new(event, "result", result);
		int ret = gateway->push_event(msg->handle, &janus_streaming_plugin, msg->transaction, event, jsep);
		JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
		g_free(sdp);
		json_decref(event);
		json_decref(jsep);
		janus_streaming_message_free(msg);
		continue;

error:
		{
			/* Prepare JSON error event */
			json_t *event = json_object();
			json_object_set_new(event, "streaming", json_string("event"));
			json_object_set_new(event, "error_code", json_integer(error_code));
			json_object_set_new(event, "error", json_string(error_cause));
			int ret = gateway->push_event(msg->handle, &janus_streaming_plugin, msg->transaction, event, NULL);
			JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
			json_decref(event);
			janus_streaming_message_free(msg);
		}
	}
	JANUS_LOG(LOG_VERB, "Leaving Streaming handler thread\n");
	return NULL;
}

/* Helpers to create a listener filedescriptor */
static int janus_streaming_create_fd(int port, in_addr_t mcast, const janus_network_address *iface,
		const char *listenername, const char *medianame, const char *mountpointname, gboolean quiet) {
	struct sockaddr_in address;
	janus_network_address_string_buffer address_representation;

	uint16_t rtp_port_next = rtp_range_slider; 					/* Read global slider */
	uint16_t rtp_port_start = rtp_port_next;
	gboolean use_range = (port == 0), rtp_port_wrap = FALSE;

	int fd = -1;
	while(1) {
		if(use_range && rtp_port_wrap && rtp_port_next >= rtp_port_start) {
			/* Full range scanned */
			JANUS_LOG(LOG_ERR, "No ports available for RTP/RTCP in range: %u -- %u\n",
				  rtp_range_min, rtp_range_max);
			break;
		}
		fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if(fd < 0) {
			JANUS_LOG(LOG_ERR, "[%s] Cannot create socket for %s...\n", mountpointname, medianame);
			break;
		}
		if(!use_range) {
			/* Use the port specified in the arguments */
			if(IN_MULTICAST(ntohl(mcast))) {
#ifdef IP_MULTICAST_ALL
				int mc_all = 0;
				if((setsockopt(fd, IPPROTO_IP, IP_MULTICAST_ALL, (void*) &mc_all, sizeof(mc_all))) < 0) {
					JANUS_LOG(LOG_ERR, "[%s] %s listener setsockopt IP_MULTICAST_ALL failed\n", mountpointname, listenername);
					close(fd);
					return -1;
				}
#endif
				struct ip_mreq mreq;
				memset(&mreq, '\0', sizeof(mreq));
				mreq.imr_multiaddr.s_addr = mcast;
				if(!janus_network_address_is_null(iface)) {
					if(iface->family == AF_INET) {
						mreq.imr_interface = iface->ipv4;
						(void) janus_network_address_to_string_buffer(iface, &address_representation); /* This is OK: if we get here iface must be non-NULL */
						JANUS_LOG(LOG_INFO, "[%s] %s listener using interface address: %s\n", mountpointname, listenername, janus_network_address_string_from_buffer(&address_representation));
					} else {
						JANUS_LOG(LOG_ERR, "[%s] %s listener: invalid multicast address type (only IPv4 is currently supported by this plugin)\n", mountpointname, listenername);
						close(fd);
						return -1;
					}
				} else {
					JANUS_LOG(LOG_WARN, "[%s] No multicast interface for: %s. This may not work as expected if you have multiple network devices (NICs)\n", mountpointname, listenername);
				}
				if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
					JANUS_LOG(LOG_ERR, "[%s] %s listener IP_ADD_MEMBERSHIP failed\n", mountpointname, listenername);
					close(fd);
					return -1;
				}
				JANUS_LOG(LOG_INFO, "[%s] %s listener IP_ADD_MEMBERSHIP ok\n", mountpointname, listenername);
			}
		} else {
			/* Pick a port in the configured range */
			port = rtp_port_next;
			if((uint32_t)(rtp_port_next) < rtp_range_max) {
				rtp_port_next++;
			} else {
				rtp_port_next = rtp_range_min;
				rtp_port_wrap = TRUE;
			}
		}
		address.sin_family = AF_INET;
		address.sin_port = htons(port);
		address.sin_addr.s_addr = INADDR_ANY;
		/* If this is multicast, allow a re-use of the same ports (different groups may be used) */
		if(!use_range && IN_MULTICAST(ntohl(mcast))) {
			int reuse = 1;
			if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
				JANUS_LOG(LOG_ERR, "[%s] %s listener setsockopt SO_REUSEADDR failed\n", mountpointname, listenername);
				close(fd);
				return -1;
			}
			address.sin_addr.s_addr = mcast;
		} else {
			if(!IN_MULTICAST(ntohl(mcast)) && !janus_network_address_is_null(iface)) {
				if(iface->family == AF_INET) {
					address.sin_addr = iface->ipv4;
					(void) janus_network_address_to_string_buffer(iface, &address_representation); /* This is OK: if we get here iface must be non-NULL */
					JANUS_LOG(LOG_INFO, "[%s] %s listener restricted to interface address: %s\n", mountpointname, listenername, janus_network_address_string_from_buffer(&address_representation));
				} else {
					JANUS_LOG(LOG_ERR, "[%s] %s listener: invalid address/restriction type (only IPv4 is currently supported by this plugin)\n", mountpointname, listenername);
					close(fd);
					fd = -1;
					continue;
				}
			}
		}
		/* Bind to the specified port */
		if(bind(fd, (struct sockaddr *)(&address), sizeof(struct sockaddr)) < 0) {
			close(fd);
			fd = -1;
			if(!use_range && !quiet) {
				JANUS_LOG(LOG_ERR, "[%s] Bind failed for %s (port %d)...\n", mountpointname, medianame, port);
				break;
			}
		} else {
			if(use_range)
				rtp_range_slider = port;	/* Update global slider */
			break;
		}
	}
	return fd;
}
/* Helper to bind RTP/RTCP port pair (for RTSP) */
static int janus_streaming_allocate_port_pair(const char *name, const char *media,
		in_addr_t mcast, const janus_network_address *iface, multiple_fds *fds, int ports[2]) {
	/* Start from the global slider */
	uint16_t rtp_port_next = rtp_range_slider;
	if(rtp_port_next % 2 != 0)	/* We want an even port for RTP */
		rtp_port_next++;
	uint16_t rtp_port_start = rtp_port_next;
	gboolean rtp_port_wrap = FALSE;

	int rtp_fd = -1, rtcp_fd = -1;
	while(1) {
		if(rtp_port_wrap && rtp_port_next >= rtp_port_start) {
			/* Full range scanned */
			JANUS_LOG(LOG_ERR, "No ports available for audio/video channel in range: %u -- %u\n",
				rtp_range_min, rtp_range_max);
			break;
		}
		int rtp_port = rtp_port_next;
		int rtcp_port = rtp_port+1;
		if((uint32_t)(rtp_port_next + 2UL) < rtp_range_max) {
			/* Advance to next pair */
			rtp_port_next += 2;
		} else {
			rtp_port_next = rtp_range_min;
			rtp_port_wrap = TRUE;
		}
		rtp_fd = janus_streaming_create_fd(rtp_port, mcast, iface, media, media, name, TRUE);
		if(rtp_fd != -1) {
			rtcp_fd = janus_streaming_create_fd(rtcp_port, mcast, iface, media, media, name, TRUE);
			if(rtcp_fd != -1) {
				/* Done */
				fds->fd = rtp_fd;
				fds->rtcp_fd = rtcp_fd;
				ports[0] = rtp_port;
				ports[1] = rtcp_port;
				/* Update global slider */
				rtp_range_slider = rtp_port_next;
				return 0;
			}
		}
		/* If we got here, something failed: try again */
		if(rtp_fd != -1)
			close(rtp_fd);
	}
	return -1;
}

/* Helper to return fd port */
static int janus_streaming_get_fd_port(int fd) {
	struct sockaddr_in server;
	socklen_t len = sizeof(server);
	if(getsockname(fd, &server, &len) == -1) {
		return -1;
	}

	return ntohs(server.sin_port);
}

/* Helpers to destroy a streaming mountpoint. */
static void janus_streaming_rtp_source_free(janus_streaming_rtp_source *source) {
	if(source->audio_fd > -1) {
		close(source->audio_fd);
	}
	if(source->video_fd[0] > -1) {
		close(source->video_fd[0]);
	}
	if(source->video_fd[1] > -1) {
		close(source->video_fd[1]);
	}
	if(source->video_fd[2] > -1) {
		close(source->video_fd[2]);
	}
	if(source->data_fd > -1) {
		close(source->data_fd);
	}
	if(source->audio_rtcp_fd > -1) {
		close(source->audio_rtcp_fd);
	}
	if(source->video_rtcp_fd > -1) {
		close(source->video_rtcp_fd);
	}
	if(source->pipefd[0] > -1) {
		close(source->pipefd[0]);
	}
	if(source->pipefd[1] > -1) {
		close(source->pipefd[1]);
	}
	janus_mutex_lock(&source->keyframe.mutex);
	if(source->keyframe.latest_keyframe != NULL)
		g_list_free_full(source->keyframe.latest_keyframe, (GDestroyNotify)janus_streaming_rtp_relay_packet_free);
	source->keyframe.latest_keyframe = NULL;
	janus_mutex_unlock(&source->keyframe.mutex);
	janus_mutex_lock(&source->buffermsg_mutex);
	if(source->last_msg != NULL)
		janus_streaming_rtp_relay_packet_free((janus_streaming_rtp_relay_packet *)source->last_msg);
	source->last_msg = NULL;
	janus_mutex_unlock(&source->buffermsg_mutex);
	if(source->is_srtp) {
		g_free(source->srtpcrypto);
		srtp_dealloc(source->srtp_ctx);
		g_free(source->srtp_policy.key);
	}
#ifdef HAVE_LIBCURL
	janus_mutex_lock(&source->rtsp_mutex);
	if(source->curl) {
		/* Send an RTSP TEARDOWN */
		curl_easy_setopt(source->curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_TEARDOWN);
		int res = curl_easy_perform(source->curl);
		if(res != CURLE_OK) {
			JANUS_LOG(LOG_ERR, "Couldn't send TEARDOWN request: %s\n", curl_easy_strerror(res));
		}
		curl_easy_cleanup(source->curl);
	}
	janus_streaming_buffer *curldata = source->curldata;
	if(curldata != NULL) {
		g_free(curldata->buffer);
		g_free(curldata);
	}
	g_free(source->rtsp_url);
	g_free(source->rtsp_username);
	g_free(source->rtsp_password);
	g_free(source->rtsp_ahost);
	g_free(source->rtsp_vhost);
	janus_mutex_unlock(&source->rtsp_mutex);
#endif
	g_free(source);
}

static void janus_streaming_file_source_free(janus_streaming_file_source *source) {
	g_free(source->filename);
	g_free(source);
}

/* Helper to create an RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
janus_streaming_mountpoint *janus_streaming_create_rtp_source(
		uint64_t id, char *name, char *desc,
		int srtpsuite, char *srtpcrypto, int threads,
		gboolean doaudio, char *amcast, const janus_network_address *aiface, uint16_t aport, uint16_t artcpport, uint8_t acodec, char *artpmap, char *afmtp, gboolean doaskew,
		gboolean dovideo, char *vmcast, const janus_network_address *viface, uint16_t vport, uint16_t vrtcpport, uint8_t vcodec, char *vrtpmap, char *vfmtp, gboolean bufferkf,
			gboolean simulcast, uint16_t vport2, uint16_t vport3, gboolean svc, gboolean dovskew, int rtp_collision,
		gboolean dodata, const janus_network_address *diface, uint16_t dport, gboolean buffermsg) {
	janus_mutex_lock(&mountpoints_mutex);
	if(id == 0) {
		JANUS_LOG(LOG_VERB, "Missing id, will generate a random one...\n");
		while(id == 0) {
			id = janus_random_uint64();
			if(g_hash_table_lookup(mountpoints, &id) != NULL) {
				/* ID already in use, try another one */
				id = 0;
			}
		}
	}
	char tempname[255];
	if(name == NULL) {
		JANUS_LOG(LOG_VERB, "Missing name, will generate a random one...\n");
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "mp-%"SCNu64, id);
	} else if(atoi(name) != 0) {
		JANUS_LOG(LOG_VERB, "Names can't start with a number, prefixing it...\n");
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "mp-%s", name);
		name = NULL;
	}
	if(!doaudio && !dovideo && !dodata) {
		JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, no audio, video or data have to be streamed...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	if(doaudio && (artpmap == NULL)) {
		JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information for audio...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	if(dovideo && (vcodec == 0 || vrtpmap == NULL)) {
		JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information for video...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "Audio %s, Video %s, Data %s\n",
		doaudio ? "enabled" : "NOT enabled",
		dovideo ? "enabled" : "NOT enabled",
		dodata ? "enabled" : "NOT enabled");
	/* First of all, let's check if the requested ports are free */
	int audio_fd = -1;
	int audio_rtcp_fd = -1;
	if(doaudio) {
		audio_fd = janus_streaming_create_fd(aport, amcast ? inet_addr(amcast) : INADDR_ANY, aiface,
			"Audio", "audio", name ? name : tempname, FALSE);
		if(audio_fd < 0) {
			JANUS_LOG(LOG_ERR, "Can't bind to port %d for audio...\n", aport);
			janus_mutex_unlock(&mountpoints_mutex);
			return NULL;
		}
		aport = janus_streaming_get_fd_port(audio_fd);
		if(artcpport > 0) {
			audio_rtcp_fd = janus_streaming_create_fd(artcpport, amcast ? inet_addr(amcast) : INADDR_ANY, aiface,
				"Audio", "audio", name ? name : tempname, FALSE);
			if(audio_rtcp_fd < 0) {
				JANUS_LOG(LOG_ERR, "Can't bind to port %d for audio rtcp...\n", aport+1);
				if(audio_fd > -1)
					close(audio_fd);
				janus_mutex_unlock(&mountpoints_mutex);
				return NULL;
			}
		}
	}
	int video_fd[3] = {-1, -1, -1};
	int video_rtcp_fd = -1;
	if(dovideo) {
		video_fd[0] = janus_streaming_create_fd(vport, vmcast ? inet_addr(vmcast) : INADDR_ANY, viface,
			"Video", "video", name ? name : tempname, FALSE);
		if(video_fd[0] < 0) {
			JANUS_LOG(LOG_ERR, "Can't bind to port %d for video...\n", vport);
			if(audio_fd > -1)
				close(audio_fd);
			if(audio_rtcp_fd > -1)
				close(audio_rtcp_fd);
			janus_mutex_unlock(&mountpoints_mutex);
			return NULL;
		}
		vport = janus_streaming_get_fd_port(video_fd[0]);
		if(vrtcpport > 0) {
			video_rtcp_fd = janus_streaming_create_fd(vrtcpport, amcast ? inet_addr(amcast) : INADDR_ANY, aiface,
				"Video", "video", name ? name : tempname, FALSE);
			if(video_rtcp_fd < 0) {
				JANUS_LOG(LOG_ERR, "Can't bind to port %d for video rtcp...\n", vport+1);
				if(audio_fd > -1)
					close(audio_fd);
				if(audio_rtcp_fd > -1)
					close(audio_rtcp_fd);
				if(video_fd[0] > -1)
					close(video_fd[0]);
				janus_mutex_unlock(&mountpoints_mutex);
				return NULL;
			}
		}
		if(simulcast) {
			if(vport2 > 0) {
				video_fd[1] = janus_streaming_create_fd(vport2, vmcast ? inet_addr(vmcast) : INADDR_ANY, viface,
					"Video", "video", name ? name : tempname, FALSE);
				if(video_fd[1] < 0) {
					JANUS_LOG(LOG_ERR, "Can't bind to port %d for video (2nd port)...\n", vport2);
					if(audio_fd > -1)
						close(audio_fd);
					if(audio_rtcp_fd > -1)
						close(audio_rtcp_fd);
					if(video_fd[0] > -1)
						close(video_fd[0]);
					if(video_rtcp_fd > -1)
						close(video_rtcp_fd);
					janus_mutex_unlock(&mountpoints_mutex);
					return NULL;
				}
				vport2 = janus_streaming_get_fd_port(video_fd[1]);
			}
			if(vport3 > 0) {
				video_fd[2] = janus_streaming_create_fd(vport3, vmcast ? inet_addr(vmcast) : INADDR_ANY, viface,
					"Video", "video", name ? name : tempname, FALSE);
				if(video_fd[2] < 0) {
					JANUS_LOG(LOG_ERR, "Can't bind to port %d for video (3rd port)...\n", vport3);
					if(audio_fd > -1)
						close(audio_fd);
					if(audio_rtcp_fd > -1)
						close(audio_rtcp_fd);
					if(video_rtcp_fd > -1)
						close(video_rtcp_fd);
					if(video_fd[0] > -1)
						close(video_fd[0]);
					if(video_fd[1] > -1)
						close(video_fd[1]);
					janus_mutex_unlock(&mountpoints_mutex);
					return NULL;
				}
				vport3 = janus_streaming_get_fd_port(video_fd[2]);
			}
		}
	}
	int data_fd = -1;
	if(dodata) {
#ifdef HAVE_SCTP
		data_fd = janus_streaming_create_fd(dport, INADDR_ANY, diface,
			"Data", "data", name ? name : tempname, FALSE);
		if(data_fd < 0) {
			JANUS_LOG(LOG_ERR, "Can't bind to port %d for data...\n", dport);
			if(audio_fd > -1)
				close(audio_fd);
			if(audio_rtcp_fd > -1)
				close(audio_rtcp_fd);
			if(video_rtcp_fd > -1)
				close(video_rtcp_fd);
			if(video_fd[0] > -1)
				close(video_fd[0]);
			if(video_fd[1] > -1)
				close(video_fd[1]);
			if(video_fd[2] > -1)
				close(video_fd[2]);
			janus_mutex_unlock(&mountpoints_mutex);
			return NULL;
		}
		dport = janus_streaming_get_fd_port(data_fd);
#else
		JANUS_LOG(LOG_WARN, "Mountpoint wants to do datachannel relaying, but datachannels support was not compiled...\n");
		dodata = FALSE;
#endif
	}
	/* Create the mountpoint */
	janus_network_address nil;
	janus_network_address_nullify(&nil);

	janus_streaming_mountpoint *live_rtp = g_malloc0(sizeof(janus_streaming_mountpoint));
	live_rtp->id = id;
	live_rtp->name = g_strdup(name ? name : tempname);
	char *description = NULL;
	if(desc != NULL)
		description = g_strdup(desc);
	else
		description = g_strdup(name ? name : tempname);
	live_rtp->description = description;
	live_rtp->enabled = TRUE;
	live_rtp->active = FALSE;
	live_rtp->audio = doaudio;
	live_rtp->video = dovideo;
	live_rtp->data = dodata;
	live_rtp->streaming_type = janus_streaming_type_live;
	live_rtp->streaming_source = janus_streaming_source_rtp;
	janus_streaming_rtp_source *live_rtp_source = g_malloc0(sizeof(janus_streaming_rtp_source));
	/* First of all, let's check if we need to setup an SRTP mountpoint */
	if(srtpsuite > 0 && srtpcrypto != NULL) {
		/* Base64 decode the crypto string and set it as the SRTP context */
		gsize len = 0;
		guchar *decoded = g_base64_decode(srtpcrypto, &len);
		if(len < SRTP_MASTER_LENGTH) {
			JANUS_LOG(LOG_ERR, "Invalid SRTP crypto (%s)\n", srtpcrypto);
			g_free(decoded);
			if(audio_fd > -1)
				close(audio_fd);
			if(audio_rtcp_fd > -1)
				close(audio_rtcp_fd);
			if(video_rtcp_fd > -1)
				close(video_rtcp_fd);
			if(video_fd[0] > -1)
				close(video_fd[0]);
			if(video_fd[1] > -1)
				close(video_fd[1]);
			if(video_fd[2] > -1)
				close(video_fd[2]);
			if(data_fd > -1)
				close(data_fd);
			janus_mutex_unlock(&mountpoints_mutex);
			g_free(live_rtp_source);
			g_free(live_rtp->name);
			g_free(live_rtp->description);
			g_free(live_rtp);
			return NULL;
		}
		/* Set SRTP policy */
		srtp_policy_t *policy = &live_rtp_source->srtp_policy;
		srtp_crypto_policy_set_rtp_default(&(policy->rtp));
		if(srtpsuite == 32) {
			srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(policy->rtp));
		} else if(srtpsuite == 80) {
			srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp));
		}
		policy->ssrc.type = ssrc_any_inbound;
		policy->key = decoded;
		policy->next = NULL;
		/* Create SRTP context */
		srtp_err_status_t res = srtp_create(&live_rtp_source->srtp_ctx, policy);
		if(res != srtp_err_status_ok) {
			/* Something went wrong... */
			JANUS_LOG(LOG_ERR, "Error creating forwarder SRTP session: %d (%s)\n", res, janus_srtp_error_str(res));
			g_free(decoded);
			if(audio_fd > -1)
				close(audio_fd);
			if(audio_rtcp_fd > -1)
				close(audio_rtcp_fd);
			if(video_rtcp_fd > -1)
				close(video_rtcp_fd);
			if(video_fd[0] > -1)
				close(video_fd[0]);
			if(video_fd[1] > -1)
				close(video_fd[1]);
			if(video_fd[2] > -1)
				close(video_fd[2]);
			if(data_fd > -1)
				close(data_fd);
			janus_mutex_unlock(&mountpoints_mutex);
			g_free(live_rtp_source);
			g_free(live_rtp->name);
			g_free(live_rtp->description);
			g_free(live_rtp);
			return NULL;
		}
		live_rtp_source->is_srtp = TRUE;
		live_rtp_source->srtpsuite = srtpsuite;
		live_rtp_source->srtpcrypto = g_strdup(srtpcrypto);
	}
	live_rtp_source->audio_mcast = doaudio ? (amcast ? inet_addr(amcast) : INADDR_ANY) : INADDR_ANY;
	live_rtp_source->audio_iface = doaudio && !janus_network_address_is_null(aiface) ? *aiface : nil;
	live_rtp_source->audio_port = doaudio ? aport : -1;
	live_rtp_source->audio_rtcp_port = artcpport;
	live_rtp_source->askew = doaskew;
	live_rtp_source->video_mcast = dovideo ? (vmcast ? inet_addr(vmcast) : INADDR_ANY) : INADDR_ANY;
	live_rtp_source->video_port[0] = dovideo ? vport : -1;
	live_rtp_source->video_rtcp_port = vrtcpport;
	live_rtp_source->simulcast = dovideo && simulcast;
	live_rtp_source->video_port[1] = live_rtp_source->simulcast ? vport2 : -1;
	live_rtp_source->video_port[2] = live_rtp_source->simulcast ? vport3 : -1;
	live_rtp_source->video_iface = dovideo && !janus_network_address_is_null(viface) ? *viface : nil;
	live_rtp_source->vskew = dovskew;
	live_rtp_source->data_port = dodata ? dport : -1;
	live_rtp_source->data_iface = dodata && !janus_network_address_is_null(diface) ? *diface : nil;
	live_rtp_source->arc = NULL;
	live_rtp_source->vrc = NULL;
	live_rtp_source->drc = NULL;
	janus_rtp_switching_context_reset(&live_rtp_source->context[0]);
	janus_rtp_switching_context_reset(&live_rtp_source->context[1]);
	janus_rtp_switching_context_reset(&live_rtp_source->context[2]);
	janus_mutex_init(&live_rtp_source->rec_mutex);
	live_rtp_source->audio_fd = audio_fd;
	live_rtp_source->audio_rtcp_fd = audio_rtcp_fd;
	live_rtp_source->video_rtcp_fd = video_rtcp_fd;
	live_rtp_source->video_fd[0] = video_fd[0];
	live_rtp_source->video_fd[1] = video_fd[1];
	live_rtp_source->video_fd[2] = video_fd[2];
	live_rtp_source->data_fd = data_fd;
	live_rtp_source->pipefd[0] = -1;
	live_rtp_source->pipefd[1] = -1;
	pipe(live_rtp_source->pipefd);
	live_rtp_source->last_received_audio = janus_get_monotonic_time();
	live_rtp_source->last_received_video = janus_get_monotonic_time();
	live_rtp_source->last_received_data = janus_get_monotonic_time();
	live_rtp_source->keyframe.enabled = bufferkf;
	live_rtp_source->keyframe.latest_keyframe = NULL;
	live_rtp_source->keyframe.temp_keyframe = NULL;
	live_rtp_source->keyframe.temp_ts = 0;
	janus_mutex_init(&live_rtp_source->keyframe.mutex);
	live_rtp_source->rtp_collision = rtp_collision;
	live_rtp_source->buffermsg = buffermsg;
	live_rtp_source->last_msg = NULL;
	janus_mutex_init(&live_rtp_source->buffermsg_mutex);
	live_rtp->source = live_rtp_source;
	live_rtp->source_destroy = (GDestroyNotify) janus_streaming_rtp_source_free;
	live_rtp->codecs.audio_pt = doaudio ? acodec : -1;
	live_rtp->codecs.audio_rtpmap = doaudio ? g_strdup(artpmap) : NULL;
	live_rtp->codecs.audio_fmtp = doaudio ? (afmtp ? g_strdup(afmtp) : NULL) : NULL;
	live_rtp->codecs.video_codec = JANUS_VIDEOCODEC_NONE;
	if(dovideo) {
		if(strstr(vrtpmap, "vp8") || strstr(vrtpmap, "VP8"))
			live_rtp->codecs.video_codec = JANUS_VIDEOCODEC_VP8;
		else if(strstr(vrtpmap, "vp9") || strstr(vrtpmap, "VP9"))
			live_rtp->codecs.video_codec = JANUS_VIDEOCODEC_VP9;
		else if(strstr(vrtpmap, "h264") || strstr(vrtpmap, "H264"))
			live_rtp->codecs.video_codec = JANUS_VIDEOCODEC_H264;
	}
	if(svc) {
		if(live_rtp->codecs.video_codec == JANUS_VIDEOCODEC_VP9) {
			live_rtp_source->svc = TRUE;
		} else {
			JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9-SVC mountpoints: disabling it...\n");
		}
	}
	live_rtp->codecs.video_pt = dovideo ? vcodec : -1;
	live_rtp->codecs.video_rtpmap = dovideo ? g_strdup(vrtpmap) : NULL;
	live_rtp->codecs.video_fmtp = dovideo ? (vfmtp ? g_strdup(vfmtp) : NULL) : NULL;
	live_rtp->viewers = NULL;
	g_atomic_int_set(&live_rtp->destroyed, 0);
	janus_refcount_init(&live_rtp->ref, janus_streaming_mountpoint_free);
	janus_mutex_init(&live_rtp->mutex);
	g_hash_table_insert(mountpoints, janus_uint64_dup(live_rtp->id), live_rtp);
	janus_mutex_unlock(&mountpoints_mutex);
	/* If we need helper threads, spawn them now */
	GError *error = NULL;
	char tname[16];
	if(threads > 0) {
		int i=0;
		for(i=0; i<threads; i++) {
			janus_streaming_helper *helper = g_malloc0(sizeof(janus_streaming_helper));
			helper->id = i+1;
			helper->mp = live_rtp;
			helper->queued_packets = g_async_queue_new_full((GDestroyNotify)janus_streaming_rtp_relay_packet_free);
			janus_mutex_init(&helper->mutex);
			live_rtp->helper_threads++;
			g_snprintf(tname, sizeof(tname), "help %u-%"SCNu64, helper->id, live_rtp->id);
			janus_refcount_increase(&live_rtp->ref);
			helper->thread = g_thread_try_new(tname, &janus_streaming_helper_thread, helper, &error);
			if(error != NULL) {
				JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the helper thread...\n",
					error->code, error->message ? error->message : "??");
				janus_refcount_decrease(&live_rtp->ref);	/* This is for the helper thread */
				janus_streaming_mountpoint_destroy(live_rtp);
				return NULL;
			}
			live_rtp->threads = g_list_append(live_rtp->threads, helper);
		}
	}
	/* Finally, create the mountpoint thread itself */
	g_snprintf(tname, sizeof(tname), "mp %"SCNu64, live_rtp->id);
	janus_refcount_increase(&live_rtp->ref);
	live_rtp->thread = g_thread_try_new(tname, &janus_streaming_relay_thread, live_rtp, &error);
	if(error != NULL) {
		JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the RTP thread...\n", error->code, error->message ? error->message : "??");
		janus_refcount_decrease(&live_rtp->ref);	/* This is for the failed thread */
		janus_streaming_mountpoint_destroy(live_rtp);
		return NULL;
	}
	return live_rtp;
}

/* Helper to create a file/ondemand live source */
janus_streaming_mountpoint *janus_streaming_create_file_source(
		uint64_t id, char *name, char *desc, char *filename,
		gboolean live, gboolean doaudio, gboolean dovideo) {
	janus_mutex_lock(&mountpoints_mutex);
	if(filename == NULL) {
		JANUS_LOG(LOG_ERR, "Can't add 'live' stream, missing filename...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	if(name == NULL) {
		JANUS_LOG(LOG_VERB, "Missing name, will generate a random one...\n");
	}
	if(id == 0) {
		JANUS_LOG(LOG_VERB, "Missing id, will generate a random one...\n");
		while(id == 0) {
			id = janus_random_uint64();
			if(g_hash_table_lookup(mountpoints, &id) != NULL) {
				/* ID already in use, try another one */
				id = 0;
			}
		}
	}
	if(!doaudio && !dovideo) {
		JANUS_LOG(LOG_ERR, "Can't add 'file' stream, no audio or video have to be streamed...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	/* FIXME We don't support video streaming from file yet */
	if(!doaudio || dovideo) {
		JANUS_LOG(LOG_ERR, "Can't add 'file' stream, we only support audio file streaming right now...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	/* TODO We should support something more than raw a-Law and mu-Law streams... */
	if(!strstr(filename, ".alaw") && !strstr(filename, ".mulaw")) {
		JANUS_LOG(LOG_ERR, "Can't add 'file' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	janus_streaming_mountpoint *file_source = g_malloc0(sizeof(janus_streaming_mountpoint));
	file_source->id = id;
	char tempname[255];
	if(!name) {
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "mp-%"SCNu64, file_source->id);
	} else if(atoi(name) != 0) {
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "mp-%s", name);
		name = NULL;
	}
	file_source->name = g_strdup(name ? name : tempname);
	char *description = NULL;
	if(desc != NULL)
		description = g_strdup(desc);
	else
		description = g_strdup(name ? name : tempname);
	file_source->description = description;
	file_source->enabled = TRUE;
	file_source->active = FALSE;
	file_source->audio = TRUE;
	file_source->video = FALSE;
	file_source->data = FALSE;
	file_source->streaming_type = live ? janus_streaming_type_live : janus_streaming_type_on_demand;
	file_source->streaming_source = janus_streaming_source_file;
	janus_streaming_file_source *file_source_source = g_malloc0(sizeof(janus_streaming_file_source));
	file_source_source->filename = g_strdup(filename);
	file_source->source = file_source_source;
	file_source->source_destroy = (GDestroyNotify) janus_streaming_file_source_free;
	file_source->codecs.audio_pt = strstr(filename, ".alaw") ? 8 : 0;
	file_source->codecs.audio_rtpmap = g_strdup(strstr(filename, ".alaw") ? "PCMA/8000" : "PCMU/8000");
	file_source->codecs.video_pt = -1;	/* FIXME We don't support video for this type yet */
	file_source->codecs.video_rtpmap = NULL;
	file_source->viewers = NULL;
	g_atomic_int_set(&file_source->destroyed, 0);
	janus_refcount_init(&file_source->ref, janus_streaming_mountpoint_free);
	janus_mutex_init(&file_source->mutex);
	g_hash_table_insert(mountpoints, janus_uint64_dup(file_source->id), file_source);
	janus_mutex_unlock(&mountpoints_mutex);
	if(live) {
		GError *error = NULL;
		char tname[16];
		g_snprintf(tname, sizeof(tname), "mp %"SCNu64, file_source->id);
		janus_refcount_increase(&file_source->ref);
		file_source->thread = g_thread_try_new(tname, &janus_streaming_filesource_thread, file_source, &error);
		if(error != NULL) {
			JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the live filesource thread...\n", error->code, error->message ? error->message : "??");
			janus_refcount_decrease(&file_source->ref);		/* This is for the failed thread */
			janus_refcount_decrease(&file_source->ref);
			return NULL;
		}
	}
	return file_source;
}

#ifdef HAVE_LIBCURL
static size_t janus_streaming_rtsp_curl_callback(void *payload, size_t size, size_t nmemb, void *data) {
	size_t realsize = size * nmemb;
	janus_streaming_buffer *buf = (struct janus_streaming_buffer *)data;
	/* (Re)allocate if needed */
	buf->buffer = realloc(buf->buffer, buf->size+realsize+1);
	/* Update the buffer */
	memcpy(&(buf->buffer[buf->size]), payload, realsize);
	buf->size += realsize;
	buf->buffer[buf->size] = 0;
	/* Done! */
	return realsize;
}

static int janus_streaming_rtsp_parse_sdp(const char *buffer, const char *name, const char *media, int *pt,
		char *transport, char *host, char *rtpmap, char *fmtp, char *control, const janus_network_address *iface, multiple_fds *fds) {
	char pattern[256];
	g_snprintf(pattern, sizeof(pattern), "m=%s", media);
	char *m = strstr(buffer, pattern);
	if(m == NULL) {
		JANUS_LOG(LOG_VERB, "[%s] no media %s...\n", name, media);
		return -1;
	}
	sscanf(m, "m=%*s %*d %*s %d", pt);
	char *s = strstr(m, "a=control:");
	if(s == NULL) {
		JANUS_LOG(LOG_ERR, "[%s] no control for %s...\n", name, media);
		return -1;
	}
	sscanf(s, "a=control:%2047s", control);
	char *r = strstr(m, "a=rtpmap:");
	if(r != NULL) {
		sscanf(r, "a=rtpmap:%*d %2047s", rtpmap);
	}
	char *f = strstr(m, "a=fmtp:");
	if(f != NULL) {
		sscanf(f, "a=fmtp:%*d %2047[^\r\n]s", fmtp);
	}
	char *c = strstr(m, "c=IN IP4");
	if(c == NULL) {
		/* No m-line c= attribute? try in the whole SDP */
		c = strstr(buffer, "c=IN IP4");
	}
	char ip[256];
	in_addr_t mcast = INADDR_ANY;
	if(c != NULL) {
		if(sscanf(c, "c=IN IP4 %[^/]", ip) != 0) {
			memcpy(host, ip, sizeof(ip));
			c = strstr(host, "\r\n");
			if(c)
				*c = '\0';
			mcast = inet_addr(ip);
		}
	}
	/* Bind two adjacent ports for RTP and RTCP */
	int ports[2];
	if(janus_streaming_allocate_port_pair(name, media, mcast, iface, fds, ports)) {
		JANUS_LOG(LOG_ERR, "[%s] Bind failed for %s...\n", name, media);
		return -1;
	}

	if(IN_MULTICAST(ntohl(mcast))) {
		g_snprintf(transport, 1024, "RTP/AVP/UDP;multicast;client_port=%d-%d", ports[0], ports[1]);
	} else {
		g_snprintf(transport, 1024, "RTP/AVP/UDP;unicast;client_port=%d-%d", ports[0], ports[1]);
	}

	return 0;
}

/* Static helper to connect to an RTSP server, considering we might do this either
 * when creating a new mountpoint, or when reconnecting after some failure */
static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp) {
	if(mp == NULL)
		return -1;
	janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)mp->source;
	if(source == NULL)
		return -1;

	char *name = mp->name;
	gboolean doaudio = mp->audio;
	gboolean dovideo = mp->video;

	CURL *curl = curl_easy_init();
	if(curl == NULL) {
		JANUS_LOG(LOG_ERR, "Can't init CURL\n");
		return -1;
	}
	if(janus_log_level > LOG_INFO)
		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
	curl_easy_setopt(curl, CURLOPT_URL, source->rtsp_url);
	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 0L);
	/* Any authentication to take into account? */
	if(source->rtsp_username && source->rtsp_password) {
		/* Point out that digest authentication is only available is libcurl >= 7.45.0 */
		if(LIBCURL_VERSION_NUM < 0x072d00) {
			JANUS_LOG(LOG_WARN, "RTSP digest authentication unsupported (needs libcurl >= 7.45.0)\n");
		}
		curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
		curl_easy_setopt(curl, CURLOPT_USERNAME, source->rtsp_username);
		curl_easy_setopt(curl, CURLOPT_PASSWORD, source->rtsp_password);
	}
	/* Send an RTSP DESCRIBE */
	janus_streaming_buffer *curldata = g_malloc(sizeof(janus_streaming_buffer));
	curldata->buffer = g_malloc0(1);
	curldata->size = 0;
	curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, source->rtsp_url);
	curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_DESCRIBE);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_streaming_rtsp_curl_callback);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, curldata);
	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, janus_streaming_rtsp_curl_callback);
	curl_easy_setopt(curl, CURLOPT_HEADERDATA, curldata);
	int res = curl_easy_perform(curl);
	if(res != CURLE_OK) {
		JANUS_LOG(LOG_ERR, "Couldn't send DESCRIBE request: %s\n", curl_easy_strerror(res));
		curl_easy_cleanup(curl);
		g_free(curldata->buffer);
		g_free(curldata);
		return -2;
	}
	long code = 0;
	res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
	if(res != CURLE_OK) {
		JANUS_LOG(LOG_ERR, "Couldn't get DESCRIBE answer: %s\n", curl_easy_strerror(res));
		curl_easy_cleanup(curl);
		g_free(curldata->buffer);
		g_free(curldata);
		return -3;
	} else if(code != 200) {
		JANUS_LOG(LOG_ERR, "Couldn't get DESCRIBE code: %ld\n", code);
		curl_easy_cleanup(curl);
		g_free(curldata->buffer);
		g_free(curldata);
		return -4;
	}
	JANUS_LOG(LOG_VERB, "DESCRIBE answer:%s\n", curldata->buffer);
	/* Parse the SDP we just got to figure out the negotiated media */
	int ka_timeout = 0;
	int vpt = -1;
	char vrtpmap[2048];
	char vfmtp[2048];
	char vcontrol[2048];
	char uri[1024];
	char vtransport[1024];
	char vhost[256];
	int vsport = 0, vsport_rtcp = 0;
	multiple_fds video_fds = {-1, -1};

	int apt = -1;
	char artpmap[2048];
	char afmtp[2048];
	char acontrol[2048];
	char atransport[1024];
	char ahost[256];
	int asport = 0, asport_rtcp = 0;
	multiple_fds audio_fds = {-1, -1};

	/* Parse both video and audio first before proceed to setup as curldata will be reused */
	int vresult;
	vresult = janus_streaming_rtsp_parse_sdp(curldata->buffer, name, "video", &vpt,
		vtransport, vhost, vrtpmap, vfmtp, vcontrol, &source->video_iface, &video_fds);

	int aresult;
	aresult = janus_streaming_rtsp_parse_sdp(curldata->buffer, name, "audio", &apt,
		atransport, ahost, artpmap, afmtp, acontrol, &source->audio_iface, &audio_fds);

	if(vresult != -1) {
		/* Send an RTSP SETUP for video */
		g_free(curldata->buffer);
		curldata->buffer = g_malloc0(1);
		curldata->size = 0;
		if(strstr(vcontrol, source->rtsp_url) == vcontrol) {
			/* The control attribute already contains the whole URL? */
			g_snprintf(uri, sizeof(uri), "%s", vcontrol);
		} else {
			/* Append the control attribute to the URL */
			g_snprintf(uri, sizeof(uri), "%s/%s", source->rtsp_url, vcontrol);
		}
		curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri);
		curl_easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, vtransport);
		curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP);
		res = curl_easy_perform(curl);
		if(res != CURLE_OK) {
			JANUS_LOG(LOG_ERR, "Couldn't send SETUP request: %s\n", curl_easy_strerror(res));
			curl_easy_cleanup(curl);
			g_free(curldata->buffer);
			g_free(curldata);
			if(video_fds.fd != -1) close(video_fds.fd);
			if(video_fds.rtcp_fd != -1) close(video_fds.rtcp_fd);
			if(audio_fds.fd != -1) close(audio_fds.fd);
			if(audio_fds.rtcp_fd != -1) close(audio_fds.rtcp_fd);
			return -5;
		} else if(code != 200) {
			JANUS_LOG(LOG_ERR, "Couldn't get SETUP code: %ld\n", code);
			curl_easy_cleanup(curl);
			g_free(curldata->buffer);
			g_free(curldata);
			if(video_fds.fd != -1) close(video_fds.fd);
			if(video_fds.rtcp_fd != -1) close(video_fds.rtcp_fd);
			if(audio_fds.fd != -1) close(audio_fds.fd);
			if(audio_fds.rtcp_fd != -1) close(audio_fds.rtcp_fd);
			return -5;
		}
		JANUS_LOG(LOG_VERB, "SETUP answer:%s\n", curldata->buffer);
		const char *timeout = strstr(curldata->buffer, ";timeout=");
		if(timeout != NULL) {
			/* There's a timeout to take into account: take note of the
			 * value for sending OPTIONS keepalives later on */
			ka_timeout = atoi(timeout+strlen(";timeout="));
			JANUS_LOG(LOG_VERB, "  -- RTSP session timeout (video): %d\n", ka_timeout);
		}
		/* Get the RTP server port, which we'll need for the latching packet */
		const char *vssrc = strstr(curldata->buffer, ";ssrc=");
		if(vssrc != NULL) {
			uint32_t ssrc = strtol(vssrc+strlen(";ssrc="), NULL, 16);
			JANUS_LOG(LOG_VERB, "  -- SSRC (video): %"SCNu32"\n", ssrc);
			source->video_ssrc = ssrc;
		}
		const char *server_ports = strstr(curldata->buffer, ";server_port=");
		if(server_ports != NULL) {
			char *dash = NULL;
			vsport = strtol(server_ports+strlen(";server_port="), &dash, 10);
			vsport_rtcp = dash ? strtol(++dash, NULL, 10) : 0;
			JANUS_LOG(LOG_VERB, "  -- RTP port (video): %d\n", vsport);
			JANUS_LOG(LOG_VERB, "  -- RTCP port (video): %d\n", vsport_rtcp);
		}
	}

	if(aresult != -1) {
		/* Send an RTSP SETUP for audio */
		g_free(curldata->buffer);
		curldata->buffer = g_malloc0(1);
		curldata->size = 0;
		if(strstr(acontrol, source->rtsp_url) == acontrol) {
			/* The control attribute already contains the whole URL? */
			g_snprintf(uri, sizeof(uri), "%s", acontrol);
		} else {
			/* Append the control attribute to the URL */
			g_snprintf(uri, sizeof(uri), "%s/%s", source->rtsp_url, acontrol);
		}
		curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri);
		curl_easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, atransport);
		curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP);
		res = curl_easy_perform(curl);
		if(res != CURLE_OK) {
			JANUS_LOG(LOG_ERR, "Couldn't send SETUP request: %s\n", curl_easy_strerror(res));
			curl_easy_cleanup(curl);
			g_free(curldata->buffer);
			g_free(curldata);
			if(video_fds.fd != -1) close(video_fds.fd);
			if(video_fds.rtcp_fd != -1) close(video_fds.rtcp_fd);
			if(audio_fds.fd != -1) close(audio_fds.fd);
			if(audio_fds.rtcp_fd != -1) close(audio_fds.rtcp_fd);
			return -6;
		} else if(code != 200) {
			JANUS_LOG(LOG_ERR, "Couldn't get SETUP code: %ld\n", code);
			curl_easy_cleanup(curl);
			g_free(curldata->buffer);
			g_free(curldata);
			if(video_fds.fd != -1) close(video_fds.fd);
			if(video_fds.rtcp_fd != -1) close(video_fds.rtcp_fd);
			if(audio_fds.fd != -1) close(audio_fds.fd);
			if(audio_fds.rtcp_fd != -1) close(audio_fds.rtcp_fd);
			return -6;
		}
		JANUS_LOG(LOG_VERB, "SETUP answer:%s\n", curldata->buffer);
		const char *timeout = strstr(curldata->buffer, ";timeout=");
		if(timeout != NULL) {
			/* There's a timeout to take into account: take note of the
			 * value for sending OPTIONS keepalives later on */
			int temp_timeout = atoi(timeout+strlen(";timeout="));
			JANUS_LOG(LOG_VERB, "  -- RTSP session timeout (audio): %d\n", temp_timeout);
			if(temp_timeout > 0 && temp_timeout < ka_timeout)
				ka_timeout = temp_timeout;
		}
		/* Get the RTP server port, which we'll need for the latching packet */
		const char *assrc = strstr(curldata->buffer, ";ssrc=");
		if(assrc != NULL) {
			uint32_t ssrc = strtol(assrc+strlen(";ssrc="), NULL, 16);
			JANUS_LOG(LOG_VERB, "  -- SSRC (audio): %"SCNu32"\n", ssrc);
			source->audio_ssrc = ssrc;
		}
		const char *server_ports = strstr(curldata->buffer, ";server_port=");
		if(server_ports != NULL) {
			char *dash = NULL;
			asport = strtol(server_ports+strlen(";server_port="), &dash, 10);
			asport_rtcp = dash ? strtol(++dash, NULL, 10) : 0;
			JANUS_LOG(LOG_VERB, "  -- RTP port (audio): %d\n", asport);
			JANUS_LOG(LOG_VERB, "  -- RTCP port (audio): %d\n", asport_rtcp);
		}
	}

	/* Update the source (but check if ptype/rtpmap/fmtp need to be overridden) */
	if(mp->codecs.audio_pt == -1)
		mp->codecs.audio_pt = doaudio ? apt : -1;
	if(mp->codecs.audio_rtpmap == NULL)
		mp->codecs.audio_rtpmap = doaudio ? g_strdup(artpmap) : NULL;
	if(mp->codecs.audio_fmtp == NULL)
		mp->codecs.audio_fmtp = doaudio ? g_strdup(afmtp) : NULL;
	if(mp->codecs.video_pt == -1)
		mp->codecs.video_pt = dovideo ? vpt : -1;
	if(mp->codecs.video_rtpmap == NULL)
		mp->codecs.video_rtpmap = dovideo ? g_strdup(vrtpmap) : NULL;
	if(mp->codecs.video_fmtp == NULL)
		mp->codecs.video_fmtp = dovideo ? g_strdup(vfmtp) : NULL;
	source->audio_fd = audio_fds.fd;
	source->audio_rtcp_fd = audio_fds.rtcp_fd;
	source->remote_audio_port = asport;
	source->remote_audio_rtcp_port = asport_rtcp;
	g_free(source->rtsp_ahost);
	if(asport > 0)
		source->rtsp_ahost = g_strdup(ahost);
	source->video_fd[0] = video_fds.fd;
	source->video_rtcp_fd = video_fds.rtcp_fd;
	source->remote_video_port = vsport;
	source->remote_video_rtcp_port = vsport_rtcp;
	g_free(source->rtsp_vhost);
	if(vsport > 0)
		source->rtsp_vhost = g_strdup(vhost);
	source->curl = curl;
	source->curldata = curldata;
	source->ka_timeout = ka_timeout;
	return 0;
}

/* Helper method to send a latching packet on an RTSP media socket */
static void janus_streaming_rtsp_latch(int fd, char *host, int port, struct sockaddr *remote) {
	/* Resolve address to get an IP */
	struct addrinfo *res = NULL;
	janus_network_address addr;
	janus_network_address_string_buffer addr_buf;
	if(getaddrinfo(host, NULL, NULL, &res) != 0 ||
			janus_network_address_from_sockaddr(res->ai_addr, &addr) != 0 ||
			janus_network_address_to_string_buffer(&addr, &addr_buf) != 0) {
		JANUS_LOG(LOG_ERR, "Could not resolve %s...\n", host);
		if(res)
			freeaddrinfo(res);
	} else {
		freeaddrinfo(res);
		/* Prepare the recipient */
		struct sockaddr_in remote4;
		struct sockaddr_in6 remote6;
		socklen_t addrlen = 0;
		if(addr.family == AF_INET) {
			memset(&remote4, 0, sizeof(remote4));
			remote4.sin_family = AF_INET;
			remote4.sin_port = htons(port);
			memcpy(&remote4.sin_addr, &addr.ipv4, sizeof(addr.ipv4));
			remote = (struct sockaddr *)(&remote4);
			addrlen = sizeof(remote4);
		} else if(addr.family == AF_INET6) {
			memset(&remote6, 0, sizeof(remote6));
			remote6.sin6_family = AF_INET6;
			remote6.sin6_port = htons(port);
			memcpy(&remote6.sin6_addr, &addr.ipv6, sizeof(addr.ipv6));
			remote6.sin6_addr = addr.ipv6;
			remote = (struct sockaddr *)(&remote6);
			addrlen = sizeof(remote6);
		}
		/* Prepare an empty RTP packet */
		janus_rtp_header rtp;
		memset(&rtp, 0, sizeof(rtp));
		rtp.version = 2;
		/* Send a couple of latching packets */
		(void)sendto(fd, &rtp, 12, 0, remote, addrlen);
		(void)sendto(fd, &rtp, 12, 0, remote, addrlen);
	}
}

/* Helper to send an RTSP PLAY (either when we create the mountpoint, or when we try reconnecting) */
static int janus_streaming_rtsp_play(janus_streaming_rtp_source *source) {
	if(source == NULL || source->curldata == NULL)
		return -1;
	/* First of all, send a latching packet to the RTSP server port(s) */
	struct sockaddr remote;
	if(source->remote_audio_port > 0 && source->audio_fd >= 0) {
		JANUS_LOG(LOG_VERB, "RTSP audio latching: %s:%d\n", source->rtsp_ahost, source->remote_audio_port);
		janus_streaming_rtsp_latch(source->audio_fd, source->rtsp_ahost, source->remote_audio_port, &remote);
		if(source->remote_audio_rtcp_port > 0 && source->audio_rtcp_fd >= 0) {
			JANUS_LOG(LOG_VERB, "  -- RTCP: %s:%d\n", source->rtsp_ahost, source->remote_audio_rtcp_port);
			janus_streaming_rtsp_latch(source->audio_rtcp_fd, source->rtsp_ahost,
				source->remote_audio_rtcp_port, &source->audio_rtcp_addr);
		}
	}
	if(source->remote_video_port > 0 && source->video_fd[0] >= 0) {
		JANUS_LOG(LOG_VERB, "RTSP video latching: %s:%d\n", source->rtsp_vhost, source->remote_video_port);
		janus_streaming_rtsp_latch(source->video_fd[0], source->rtsp_vhost, source->remote_video_port, &remote);
		if(source->remote_video_rtcp_port > 0 && source->video_rtcp_fd >= 0) {
			JANUS_LOG(LOG_VERB, "  -- RTCP: %s:%d\n", source->rtsp_vhost, source->remote_video_rtcp_port);
			janus_streaming_rtsp_latch(source->video_rtcp_fd, source->rtsp_vhost,
				source->remote_video_rtcp_port, &source->video_rtcp_addr);
		}
	}
	/* Send an RTSP PLAY */
	janus_mutex_lock(&source->rtsp_mutex);
	g_free(source->curldata->buffer);
	source->curldata->buffer = g_malloc0(1);
	source->curldata->size = 0;
	JANUS_LOG(LOG_VERB, "Sending PLAY request...\n");
	curl_easy_setopt(source->curl, CURLOPT_RTSP_STREAM_URI, source->rtsp_url);
	curl_easy_setopt(source->curl, CURLOPT_RANGE, "npt=0.000-");
	curl_easy_setopt(source->curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_PLAY);
	int res = curl_easy_perform(source->curl);
	if(res != CURLE_OK) {
		JANUS_LOG(LOG_ERR, "Couldn't send PLAY request: %s\n", curl_easy_strerror(res));
		janus_mutex_unlock(&source->rtsp_mutex);
		return -1;
	}
	JANUS_LOG(LOG_VERB, "PLAY answer:%s\n", source->curldata->buffer);
	janus_mutex_unlock(&source->rtsp_mutex);
	return 0;
}

/* Helper to create an RTSP source */
janus_streaming_mountpoint *janus_streaming_create_rtsp_source(
		uint64_t id, char *name, char *desc,
		char *url, char *username, char *password,
		gboolean doaudio, int acodec, char *artpmap, char *afmtp,
		gboolean dovideo, int vcodec, char *vrtpmap, char *vfmtp,
		const janus_network_address *iface,
		gboolean error_on_failure) {
	if(url == NULL) {
		JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream, missing url...\n");
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled");

	janus_mutex_lock(&mountpoints_mutex);
	/* Create an RTP source for the media we'll get */
	if(id == 0) {
		JANUS_LOG(LOG_VERB, "Missing id, will generate a random one...\n");
		while(id == 0) {
			id = janus_random_uint64();
			if(g_hash_table_lookup(mountpoints, &id) != NULL) {
				/* ID already in use, try another one */
				id = 0;
			}
		}
	}
	char tempname[255];
	if(name == NULL) {
		JANUS_LOG(LOG_VERB, "Missing name, will generate a random one...\n");
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "%"SCNu64, id);
	} else if(atoi(name) != 0) {
		JANUS_LOG(LOG_VERB, "Names can't start with a number, prefixing it...\n");
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "mp-%s", name);
		name = NULL;
	}
	char *sourcename =  g_strdup(name ? name : tempname);
	char *description = NULL;
	if(desc != NULL) {
		description = g_strdup(desc);
	} else {
		description = g_strdup(name ? name : tempname);
	}

	janus_network_address nil;
	janus_network_address_nullify(&nil);

	/* Create the mountpoint and prepare the source */
	janus_streaming_mountpoint *live_rtsp = g_malloc0(sizeof(janus_streaming_mountpoint));
	live_rtsp->id = id;
	live_rtsp->name = sourcename;
	live_rtsp->description = description;
	live_rtsp->enabled = TRUE;
	live_rtsp->active = FALSE;
	live_rtsp->audio = doaudio;
	live_rtsp->video = dovideo;
	live_rtsp->data = FALSE;
	live_rtsp->streaming_type = janus_streaming_type_live;
	live_rtsp->streaming_source = janus_streaming_source_rtp;
	janus_streaming_rtp_source *live_rtsp_source = g_malloc0(sizeof(janus_streaming_rtp_source));
	live_rtsp_source->rtsp = TRUE;
	live_rtsp_source->rtsp_url = g_strdup(url);
	live_rtsp_source->rtsp_username = username ? g_strdup(username) : NULL;
	live_rtsp_source->rtsp_password = password ? g_strdup(password) : NULL;
	live_rtsp_source->arc = NULL;
	live_rtsp_source->vrc = NULL;
	live_rtsp_source->drc = NULL;
	live_rtsp_source->audio_fd = -1;
	live_rtsp_source->audio_rtcp_fd = -1;
	live_rtsp_source->audio_iface = iface ? *iface : nil;
	live_rtsp_source->video_fd[0] = -1;
	live_rtsp_source->video_fd[1] = -1;
	live_rtsp_source->video_fd[2] = -1;
	live_rtsp_source->video_rtcp_fd = -1;
	live_rtsp_source->video_iface = iface ? *iface : nil;
	live_rtsp_source->data_fd = -1;
	live_rtsp_source->pipefd[0] = -1;
	live_rtsp_source->pipefd[1] = -1;
	pipe(live_rtsp_source->pipefd);
	live_rtsp_source->data_iface = nil;
	live_rtsp_source->reconnect_timer = 0;
	janus_mutex_init(&live_rtsp_source->rtsp_mutex);
	live_rtsp->source = live_rtsp_source;
	live_rtsp->source_destroy = (GDestroyNotify) janus_streaming_rtp_source_free;
	live_rtsp->viewers = NULL;
	g_atomic_int_set(&live_rtsp->destroyed, 0);
	janus_refcount_init(&live_rtsp->ref, janus_streaming_mountpoint_free);
	janus_mutex_init(&live_rtsp->mutex);
	/* We may have to override the payload type and/or rtpmap and/or fmtp for audio and/or video */
	live_rtsp->codecs.audio_pt = doaudio ? acodec : -1;
	live_rtsp->codecs.audio_rtpmap = doaudio ? (artpmap ? g_strdup(artpmap) : NULL) : NULL;
	live_rtsp->codecs.audio_fmtp = doaudio ? (afmtp ? g_strdup(afmtp) : NULL) : NULL;
	live_rtsp->codecs.video_pt = dovideo ? vcodec : -1;
	live_rtsp->codecs.video_rtpmap = dovideo ? (vrtpmap ? g_strdup(vrtpmap) : NULL) : NULL;
	live_rtsp->codecs.video_fmtp = dovideo ? (vfmtp ? g_strdup(vfmtp) : NULL) : NULL;
	/* If we need to return an error on failure, try connecting right now */
	if(error_on_failure) {
		/* Now connect to the RTSP server */
		if(janus_streaming_rtsp_connect_to_server(live_rtsp) < 0) {
			/* Error connecting, get rid of the mountpoint */
			janus_mutex_unlock(&mountpoints_mutex);
			janus_refcount_decrease(&live_rtsp->ref);
			return NULL;
		}
		/* Send an RTSP PLAY, now */
		if(janus_streaming_rtsp_play(live_rtsp_source) < 0) {
			/* Error trying to play, get rid of the mountpoint */
			janus_mutex_unlock(&mountpoints_mutex);
			janus_refcount_decrease(&live_rtsp->ref);
			return NULL;
		}
	}
	/* Start the thread that will receive the media packets */
	GError *error = NULL;
	char tname[16];
	g_snprintf(tname, sizeof(tname), "mp %"SCNu64, live_rtsp->id);
	janus_refcount_increase(&live_rtsp->ref);
	live_rtsp->thread = g_thread_try_new(tname, &janus_streaming_relay_thread, live_rtsp, &error);
	if(error != NULL) {
		JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the RTSP thread...\n", error->code, error->message ? error->message : "??");
		janus_mutex_unlock(&mountpoints_mutex);
		janus_refcount_decrease(&live_rtsp->ref);	/* This is for the failed thread */
		janus_refcount_decrease(&live_rtsp->ref);
		return NULL;
	}
	g_hash_table_insert(mountpoints, janus_uint64_dup(live_rtsp->id), live_rtsp);
	janus_mutex_unlock(&mountpoints_mutex);
	return live_rtsp;
}
#else
/* Helper to create an RTSP source */
janus_streaming_mountpoint *janus_streaming_create_rtsp_source(
		uint64_t id, char *name, char *desc,
		char *url, char *username, char *password,
		gboolean doaudio, int acodec, char *audiortpmap, char *audiofmtp,
		gboolean dovideo, int vcodec, char *videortpmap, char *videofmtp,
		const janus_network_address *iface,
		gboolean error_on_failure) {
	JANUS_LOG(LOG_ERR, "RTSP need libcurl\n");
	return NULL;
}
#endif

/* FIXME Thread to send RTP packets from a file (on demand) */
static void *janus_streaming_ondemand_thread(void *data) {
	JANUS_LOG(LOG_VERB, "Filesource (on demand) RTP thread starting...\n");
	janus_streaming_session *session = (janus_streaming_session *)data;
	if(!session) {
		JANUS_LOG(LOG_ERR, "Invalid session!\n");
		g_thread_unref(g_thread_self());
		return NULL;
	}
	janus_streaming_mountpoint *mountpoint = session->mountpoint;
	if(!mountpoint) {
		JANUS_LOG(LOG_ERR, "Invalid mountpoint!\n");
		janus_refcount_decrease(&session->ref);
		g_thread_unref(g_thread_self());
		return NULL;
	}
	if(mountpoint->streaming_source != janus_streaming_source_file) {
		JANUS_LOG(LOG_ERR, "[%s] Not an file source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&session->ref);
		janus_refcount_decrease(&mountpoint->ref);
		g_thread_unref(g_thread_self());
		return NULL;
	}
	if(mountpoint->streaming_type != janus_streaming_type_on_demand) {
		JANUS_LOG(LOG_ERR, "[%s] Not an on-demand file source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&session->ref);
		janus_refcount_decrease(&mountpoint->ref);
		g_thread_unref(g_thread_self());
		return NULL;
	}
	janus_streaming_file_source *source = mountpoint->source;
	if(source == NULL || source->filename == NULL) {
		JANUS_LOG(LOG_ERR, "[%s] Invalid file source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&session->ref);
		janus_refcount_decrease(&mountpoint->ref);
		g_thread_unref(g_thread_self());
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "[%s] Opening file source %s...\n", mountpoint->name, source->filename);
	FILE *audio = fopen(source->filename, "rb");
	if(!audio) {
		JANUS_LOG(LOG_ERR, "[%s] Ooops, audio file missing!\n", mountpoint->name);
		janus_refcount_decrease(&session->ref);
		janus_refcount_decrease(&mountpoint->ref);
		g_thread_unref(g_thread_self());
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "[%s] Streaming audio file: %s\n", mountpoint->name, source->filename);
	/* Buffer */
	char *buf = g_malloc0(1024);
	char *name = g_strdup(mountpoint->name ? mountpoint->name : "??");
	/* Set up RTP */
	gint16 seq = 1;
	gint32 ts = 0;
	janus_rtp_header *header = (janus_rtp_header *)buf;
	header->version = 2;
	header->markerbit = 1;
	header->type = mountpoint->codecs.audio_pt;
	header->seq_number = htons(seq);
	header->timestamp = htonl(ts);
	header->ssrc = htonl(1);	/* The gateway will fix this anyway */
	/* Timer */
	struct timeval now, before;
	gettimeofday(&before, NULL);
	now.tv_sec = before.tv_sec;
	now.tv_usec = before.tv_usec;
	time_t passed, d_s, d_us;
	/* Loop */
	gint read = 0;
	janus_streaming_rtp_relay_packet packet;
	while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&mountpoint->destroyed) && !session->stopping && !g_atomic_int_get(&session->destroyed)) {
		/* See if it's time to prepare a frame */
		gettimeofday(&now, NULL);
		d_s = now.tv_sec - before.tv_sec;
		d_us = now.tv_usec - before.tv_usec;
		if(d_us < 0) {
			d_us += 1000000;
			--d_s;
		}
		passed = d_s*1000000 + d_us;
		if(passed < 18000) {	/* Let's wait about 18ms */
			g_usleep(5000);
			continue;
		}
		/* Update the reference time */
		before.tv_usec += 20000;
		if(before.tv_usec > 1000000) {
			before.tv_sec++;
			before.tv_usec -= 1000000;
		}
		/* If not started or paused, wait some more */
		if(!session->started || session->paused || !mountpoint->enabled)
			continue;
		/* Read frame from file... */
		read = fread(buf + RTP_HEADER_SIZE, sizeof(char), 160, audio);
		if(feof(audio)) {
			/* FIXME We're doing this forever... should this be configurable? */
			JANUS_LOG(LOG_VERB, "[%s] Rewind! (%s)\n", name, source->filename);
			fseek(audio, 0, SEEK_SET);
			continue;
		}
		if(read < 0)
			break;
		if(mountpoint->active == FALSE)
			mountpoint->active = TRUE;
		//~ JANUS_LOG(LOG_VERB, " ... Preparing RTP packet (pt=%u, seq=%u, ts=%u)...\n",
			//~ header->type, ntohs(header->seq_number), ntohl(header->timestamp));
		//~ JANUS_LOG(LOG_VERB, " ... Read %d bytes from the audio file...\n", read);
		/* Relay on all sessions */
		packet.data = header;
		packet.length = RTP_HEADER_SIZE + read;
		packet.is_rtp = TRUE;
		packet.is_video = FALSE;
		packet.is_keyframe = FALSE;
		/* Backup the actual timestamp and sequence number */
		packet.timestamp = ntohl(packet.data->timestamp);
		packet.seq_number = ntohs(packet.data->seq_number);
		/* Go! */
		janus_streaming_relay_rtp_packet(session, &packet);
		/* Update header */
		seq++;
		header->seq_number = htons(seq);
		ts += 160;
		header->timestamp = htonl(ts);
		header->markerbit = 0;
	}
	JANUS_LOG(LOG_VERB, "[%s] Leaving filesource (ondemand) thread\n", name);
	g_free(name);
	g_free(buf);
	fclose(audio);
	janus_refcount_decrease(&session->ref);
	janus_refcount_decrease(&mountpoint->ref);
	g_thread_unref(g_thread_self());
	return NULL;
}

/* FIXME Thread to send RTP packets from a file (live) */
static void *janus_streaming_filesource_thread(void *data) {
	JANUS_LOG(LOG_VERB, "Filesource (live) thread starting...\n");
	janus_streaming_mountpoint *mountpoint = (janus_streaming_mountpoint *)data;
	if(!mountpoint) {
		JANUS_LOG(LOG_ERR, "Invalid mountpoint!\n");
		return NULL;
	}
	if(mountpoint->streaming_source != janus_streaming_source_file) {
		JANUS_LOG(LOG_ERR, "[%s] Not an file source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&mountpoint->ref);
		return NULL;
	}
	if(mountpoint->streaming_type != janus_streaming_type_live) {
		JANUS_LOG(LOG_ERR, "[%s] Not a live file source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&mountpoint->ref);
		return NULL;
	}
	janus_streaming_file_source *source = mountpoint->source;
	if(source == NULL || source->filename == NULL) {
		JANUS_LOG(LOG_ERR, "[%s] Invalid file source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&mountpoint->ref);
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "[%s] Opening file source %s...\n", mountpoint->name, source->filename);
	FILE *audio = fopen(source->filename, "rb");
	if(!audio) {
		JANUS_LOG(LOG_ERR, "[%s] Ooops, audio file missing!\n", mountpoint->name);
		janus_refcount_decrease(&mountpoint->ref);
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "[%s] Streaming audio file: %s\n", mountpoint->name, source->filename);
	/* Buffer */
	char *buf = g_malloc0(1024);
	char *name = g_strdup(mountpoint->name ? mountpoint->name : "??");
	/* Set up RTP */
	gint16 seq = 1;
	gint32 ts = 0;
	janus_rtp_header *header = (janus_rtp_header *)buf;
	header->version = 2;
	header->markerbit = 1;
	header->type = mountpoint->codecs.audio_pt;
	header->seq_number = htons(seq);
	header->timestamp = htonl(ts);
	header->ssrc = htonl(1);	/* The Janus core will fix this anyway */
	/* Timer */
	struct timeval now, before;
	gettimeofday(&before, NULL);
	now.tv_sec = before.tv_sec;
	now.tv_usec = before.tv_usec;
	time_t passed, d_s, d_us;
	/* Loop */
	gint read = 0;
	janus_streaming_rtp_relay_packet packet;
	while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&mountpoint->destroyed)) {
		/* See if it's time to prepare a frame */
		gettimeofday(&now, NULL);
		d_s = now.tv_sec - before.tv_sec;
		d_us = now.tv_usec - before.tv_usec;
		if(d_us < 0) {
			d_us += 1000000;
			--d_s;
		}
		passed = d_s*1000000 + d_us;
		if(passed < 18000) {	/* Let's wait about 18ms */
			g_usleep(5000);
			continue;
		}
		/* Update the reference time */
		before.tv_usec += 20000;
		if(before.tv_usec > 1000000) {
			before.tv_sec++;
			before.tv_usec -= 1000000;
		}
		/* If paused, wait some more */
		if(!mountpoint->enabled)
			continue;
		/* Read frame from file... */
		read = fread(buf + RTP_HEADER_SIZE, sizeof(char), 160, audio);
		if(feof(audio)) {
			/* FIXME We're doing this forever... should this be configurable? */
			JANUS_LOG(LOG_VERB, "[%s] Rewind! (%s)\n", name, source->filename);
			fseek(audio, 0, SEEK_SET);
			continue;
		}
		if(read < 0)
			break;
		if(mountpoint->active == FALSE)
			mountpoint->active = TRUE;
		// JANUS_LOG(LOG_VERB, " ... Preparing RTP packet (pt=%u, seq=%u, ts=%u)...\n",
			// header->type, ntohs(header->seq_number), ntohl(header->timestamp));
		// JANUS_LOG(LOG_VERB, " ... Read %d bytes from the audio file...\n", read);
		/* Relay on all sessions */
		packet.data = header;
		packet.length = RTP_HEADER_SIZE + read;
		packet.is_rtp = TRUE;
		packet.is_video = FALSE;
		packet.is_keyframe = FALSE;
		/* Backup the actual timestamp and sequence number */
		packet.timestamp = ntohl(packet.data->timestamp);
		packet.seq_number = ntohs(packet.data->seq_number);
		/* Go! */
		janus_mutex_lock_nodebug(&mountpoint->mutex);
		g_list_foreach(mountpoint->viewers, janus_streaming_relay_rtp_packet, &packet);
		janus_mutex_unlock_nodebug(&mountpoint->mutex);
		/* Update header */
		seq++;
		header->seq_number = htons(seq);
		ts += 160;
		header->timestamp = htonl(ts);
		header->markerbit = 0;
	}
	JANUS_LOG(LOG_VERB, "[%s] Leaving filesource (live) thread\n", name);
	g_free(name);
	g_free(buf);
	fclose(audio);
	janus_refcount_decrease(&mountpoint->ref);
	return NULL;
}

/* Thread to relay RTP frames coming from gstreamer/ffmpeg/others */
static void *janus_streaming_relay_thread(void *data) {
	JANUS_LOG(LOG_VERB, "Starting streaming relay thread\n");
	janus_streaming_mountpoint *mountpoint = (janus_streaming_mountpoint *)data;
	if(!mountpoint) {
		JANUS_LOG(LOG_ERR, "Invalid mountpoint!\n");
		return NULL;
	}
	if(mountpoint->streaming_source != janus_streaming_source_rtp) {
		janus_refcount_decrease(&mountpoint->ref);
		JANUS_LOG(LOG_ERR, "[%s] Not an RTP source mountpoint!\n", mountpoint->name);
		return NULL;
	}
	janus_streaming_rtp_source *source = mountpoint->source;
	if(source == NULL) {
		JANUS_LOG(LOG_ERR, "[%s] Invalid RTP source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&mountpoint->ref);
		return NULL;
	}
	int audio_fd = source->audio_fd;
	int video_fd[3] = {source->video_fd[0], source->video_fd[1], source->video_fd[2]};
	int data_fd = source->data_fd;
	int pipe_fd = source->pipefd[0];
	int audio_rtcp_fd = source->audio_rtcp_fd;
	int video_rtcp_fd = source->video_rtcp_fd;
	char *name = g_strdup(mountpoint->name ? mountpoint->name : "??");
	/* Needed to fix seq and ts */
	uint32_t ssrc = 0, a_last_ssrc = 0, v_last_ssrc[3] = {0, 0, 0};
	/* File descriptors */
	socklen_t addrlen;
	struct sockaddr remote;
	int resfd = 0, bytes = 0;
	struct pollfd fds[8];
	char buffer[1500];
	memset(buffer, 0, 1500);
#ifdef HAVE_LIBCURL
	/* In case this is an RTSP restreamer, we may have to send keep-alives from time to time */
	gint64 now = janus_get_monotonic_time(), before = now, ka_timeout = 0;
	if(source->rtsp) {
		source->reconnect_timer = now;
		ka_timeout = ((gint64)source->ka_timeout*G_USEC_PER_SEC)/2;
	}
#endif
	/* Loop */
	int num = 0;
	janus_streaming_rtp_relay_packet packet;
	while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&mountpoint->destroyed)) {
#ifdef HAVE_LIBCURL
		/* Let's check regularly if the RTSP server seems to be gone */
		if(source->rtsp) {
			if(source->reconnecting) {
				/* We're still reconnecting, wait some more */
				g_usleep(250000);
				continue;
			}
			now = janus_get_monotonic_time();
			if(!source->reconnecting && (now - source->reconnect_timer > 5*G_USEC_PER_SEC)) {
				/* 5 seconds passed and no media? Assume the RTSP server has gone and schedule a reconnect */
				JANUS_LOG(LOG_WARN, "[%s] %"SCNi64"s passed with no media, trying to reconnect the RTSP stream\n",
					name, (now - source->reconnect_timer)/G_USEC_PER_SEC);
				audio_fd = -1;
				video_fd[0] = -1;
				video_fd[1] = -1;
				video_fd[2] = -1;
				data_fd = -1;
				source->reconnect_timer = now;
				source->reconnecting = TRUE;
				/* Let's clean up the source first */
				curl_easy_cleanup(source->curl);
				source->curl = NULL;
				if(source->curldata)
					g_free(source->curldata->buffer);
				g_free(source->curldata);
				source->curldata = NULL;
				if(source->audio_fd > -1) {
					close(source->audio_fd);
				}
				source->audio_fd = -1;
				if(source->video_fd[0] > -1) {
					close(source->video_fd[0]);
				}
				source->video_fd[0] = -1;
				if(source->video_fd[1] > -1) {
					close(source->video_fd[1]);
				}
				source->video_fd[1] = -1;
				if(source->video_fd[2] > -1) {
					close(source->video_fd[2]);
				}
				source->video_fd[2] = -1;
				if(source->data_fd > -1) {
					close(source->data_fd);
				}
				source->data_fd = -1;
				if(source->audio_rtcp_fd > -1) {
					close(source->audio_rtcp_fd);
				}
				source->audio_rtcp_fd = -1;
				if(source->video_rtcp_fd > -1) {
					close(source->video_rtcp_fd);
				}
				source->video_rtcp_fd = -1;
				/* Now let's try to reconnect */
				if(janus_streaming_rtsp_connect_to_server(mountpoint) < 0) {
					/* Reconnection failed? Let's try again later */
					JANUS_LOG(LOG_WARN, "[%s] Reconnection of the RTSP stream failed, trying again in a few seconds...\n", name);
				} else {
					/* We're connected, let's send a PLAY */
					if(janus_streaming_rtsp_play(source) < 0) {
						/* Error trying to play? Let's try again later */
						JANUS_LOG(LOG_WARN, "[%s] RTSP PLAY failed, trying again in a few seconds...\n", name);
					} else {
						/* Everything should be back to normal, let's update the file descriptors */
						JANUS_LOG(LOG_INFO, "[%s] Reconnected to the RTSP server, streaming again\n", name);
						audio_fd = source->audio_fd;
						video_fd[0] = source->video_fd[0];
						data_fd = source->data_fd;
						audio_rtcp_fd = source->audio_rtcp_fd;
						video_rtcp_fd = source->video_rtcp_fd;
						ka_timeout = ((gint64)source->ka_timeout*G_USEC_PER_SEC)/2;
					}
				}
				source->reconnect_timer = janus_get_monotonic_time();
				source->reconnecting = FALSE;
				continue;
			}
		}
		if(audio_fd < 0 && video_fd[0] < 0 && video_fd[1] < 0 && video_fd[2] < 0 && data_fd < 0) {
			/* No socket, we may be in the process of reconnecting, or waiting to reconnect */
			g_usleep(5000000);
			continue;
		}
		/* We may also need to occasionally send a OPTIONS request as a keep-alive */
		if(ka_timeout > 0) {
			/* Let's be conservative and send a OPTIONS when half of the timeout has passed */
			now = janus_get_monotonic_time();
			if(now-before > ka_timeout && source->curldata) {
				JANUS_LOG(LOG_VERB, "[%s] %"SCNi64"s passed, sending OPTIONS\n", name, (now-before)/G_USEC_PER_SEC);
				before = now;
				/* Send an RTSP OPTIONS */
				janus_mutex_lock(&source->rtsp_mutex);
				g_free(source->curldata->buffer);
				source->curldata->buffer = g_malloc0(1);
				source->curldata->size = 0;
				curl_easy_setopt(source->curl, CURLOPT_RTSP_STREAM_URI, source->rtsp_url);
				curl_easy_setopt(source->curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS);
				resfd = curl_easy_perform(source->curl);
				if(resfd != CURLE_OK) {
					JANUS_LOG(LOG_ERR, "[%s] Couldn't send OPTIONS request: %s\n", name, curl_easy_strerror(resfd));
				}
				janus_mutex_unlock(&source->rtsp_mutex);
			}
		}
#endif
		/* Any PLI and/or REMB we should send back to the source? */
		if(g_atomic_int_get(&source->need_pli))
			janus_streaming_rtcp_pli_send(source);
		if(source->video_rtcp_fd > -1 && source->lowest_bitrate > 0) {
			gint64 now = janus_get_monotonic_time();
			if(source->remb_latest == 0)
				source->remb_latest = now;
			else if(now - source->remb_latest >= G_USEC_PER_SEC)
				janus_streaming_rtcp_remb_send(source);
		}
		/* Prepare poll */
		num = 0;
		if(audio_fd != -1) {
			fds[num].fd = audio_fd;
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(video_fd[0] != -1) {
			fds[num].fd = video_fd[0];
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(video_fd[1] != -1) {
			fds[num].fd = video_fd[1];
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(video_fd[2] != -1) {
			fds[num].fd = video_fd[2];
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(data_fd != -1) {
			fds[num].fd = data_fd;
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(pipe_fd != -1) {
			fds[num].fd = pipe_fd;
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(audio_rtcp_fd != -1) {
			fds[num].fd = audio_rtcp_fd;
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		if(video_rtcp_fd != -1) {
			fds[num].fd = video_rtcp_fd;
			fds[num].events = POLLIN;
			fds[num].revents = 0;
			num++;
		}
		/* Wait for some data */
		resfd = poll(fds, num, 1000);
		if(resfd < 0) {
			if(errno == EINTR) {
				JANUS_LOG(LOG_HUGE, "[%s] Got an EINTR (%s), ignoring...\n", name, strerror(errno));
				continue;
			}
			JANUS_LOG(LOG_ERR, "[%s] Error polling... %d (%s)\n", name, errno, strerror(errno));
			mountpoint->enabled = FALSE;
			break;
		} else if(resfd == 0) {
			/* No data, keep going */
			continue;
		}
		int i = 0;
		for(i=0; i<num; i++) {
			if(fds[i].revents & (POLLERR | POLLHUP)) {
				/* Socket error? */
				JANUS_LOG(LOG_ERR, "[%s] Error polling: %s... %d (%s)\n", name,
					fds[i].revents & POLLERR ? "POLLERR" : "POLLHUP", errno, strerror(errno));
				mountpoint->enabled = FALSE;
				break;
			} else if(fds[i].revents & POLLIN) {
				/* Got an RTP or data packet */
				if(pipe_fd != -1 && fds[i].fd == pipe_fd) {
					/* We're done here */
					int code = 0;
					bytes = read(pipe_fd, &code, sizeof(int));
					JANUS_LOG(LOG_VERB, "[%s] Interrupting mountpoint\n", mountpoint->name);
					break;
				} else if(audio_fd != -1 && fds[i].fd == audio_fd) {
					/* Got something audio (RTP) */
					if(mountpoint->active == FALSE)
						mountpoint->active = TRUE;
					gint64 now = janus_get_monotonic_time();
#ifdef HAVE_LIBCURL
					source->reconnect_timer = now;
#endif
					addrlen = sizeof(remote);
					bytes = recvfrom(audio_fd, buffer, 1500, 0, &remote, &addrlen);
					if(bytes < 0 || !janus_is_rtp(buffer, bytes)) {
						/* Failed to read or not an RTP packet? */
						continue;
					}
					janus_rtp_header *rtp = (janus_rtp_header *)buffer;
					ssrc = ntohl(rtp->ssrc);
					if(source->rtp_collision > 0 && a_last_ssrc && ssrc != a_last_ssrc &&
							(now-source->last_received_audio) < (gint64)1000*source->rtp_collision) {
						JANUS_LOG(LOG_WARN, "[%s] RTP collision on audio mountpoint, dropping packet (ssrc=%"SCNu32")\n", name, ssrc);
						continue;
					}
					source->last_received_audio = now;
					//~ JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the audio channel...\n", bytes);
					/* If paused, ignore this packet */
					if(!mountpoint->enabled)
						continue;
					/* Is this SRTP? */
					if(source->is_srtp) {
						int buflen = bytes;
						srtp_err_status_t res = srtp_unprotect(source->srtp_ctx, buffer, &buflen);
						//~ if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
						if(res != srtp_err_status_ok) {
							guint32 timestamp = ntohl(rtp->timestamp);
							guint16 seq = ntohs(rtp->seq_number);
							JANUS_LOG(LOG_ERR, "[%s] Audio SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
								name, janus_srtp_error_str(res), bytes, buflen, timestamp, seq);
							continue;
						}
						bytes = buflen;
					}
					//~ JANUS_LOG(LOG_VERB, " ... parsed RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n",
						//~ ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp));
					/* Relay on all sessions */
					packet.data = rtp;
					packet.length = bytes;
					packet.is_rtp = TRUE;
					packet.is_video = FALSE;
					packet.is_keyframe = FALSE;
					/* Do we have a new stream? */
					if(ssrc != a_last_ssrc) {
						source->audio_ssrc = a_last_ssrc = ssrc;
						JANUS_LOG(LOG_INFO, "[%s] New audio stream! (ssrc=%"SCNu32")\n", name, a_last_ssrc);
					}
					packet.data->type = mountpoint->codecs.audio_pt;
					/* Is there a recorder? */
					janus_rtp_header_update(packet.data, &source->context[0], FALSE, 0);
					if(source->askew) {
						int ret = janus_rtp_skew_compensate_audio(packet.data, &source->context[0], now);
						if(ret < 0) {
							JANUS_LOG(LOG_WARN, "[%s] Dropping %d packets, audio source clock is too fast (ssrc=%"SCNu32")\n",
								name, -ret, a_last_ssrc);
							continue;
						} else if(ret > 0) {
							JANUS_LOG(LOG_WARN, "[%s] Jumping %d RTP sequence numbers, audio source clock is too slow (ssrc=%"SCNu32")\n",
								name, ret, a_last_ssrc);
						}
					}
					packet.data->ssrc = htonl((uint32_t)mountpoint->id);
					janus_recorder_save_frame(source->arc, buffer, bytes);
					packet.data->ssrc = htonl(ssrc);
					/* Backup the actual timestamp and sequence number set by the restreamer, in case switching is involved */
					packet.timestamp = ntohl(packet.data->timestamp);
					packet.seq_number = ntohs(packet.data->seq_number);
					/* Go! */
					janus_mutex_lock(&mountpoint->mutex);
					g_list_foreach(mountpoint->helper_threads == 0 ? mountpoint->viewers : mountpoint->threads,
						mountpoint->helper_threads == 0 ? janus_streaming_relay_rtp_packet : janus_streaming_helper_rtprtcp_packet,
						&packet);
					janus_mutex_unlock(&mountpoint->mutex);
					continue;
				} else if((video_fd[0] != -1 && fds[i].fd == video_fd[0]) ||
						(video_fd[1] != -1 && fds[i].fd == video_fd[1]) ||
						(video_fd[2] != -1 && fds[i].fd == video_fd[2])) {
					/* Got something video (RTP) */
					int index = -1;
					if(fds[i].fd == video_fd[0])
						index = 0;
					else if(fds[i].fd == video_fd[1])
						index = 1;
					else if(fds[i].fd == video_fd[2])
						index = 2;
					if(mountpoint->active == FALSE)
						mountpoint->active = TRUE;
					gint64 now = janus_get_monotonic_time();
#ifdef HAVE_LIBCURL
					source->reconnect_timer = now;
#endif
					addrlen = sizeof(remote);
					bytes = recvfrom(fds[i].fd, buffer, 1500, 0, &remote, &addrlen);
					if(bytes < 0 || !janus_is_rtp(buffer, bytes)) {
						/* Failed to read or not an RTP packet? */
						continue;
					}
					janus_rtp_header *rtp = (janus_rtp_header *)buffer;
					ssrc = ntohl(rtp->ssrc);
					if(source->rtp_collision > 0 && v_last_ssrc[index] && ssrc != v_last_ssrc[index] &&
							(now-source->last_received_video) < (gint64)1000*source->rtp_collision) {
						JANUS_LOG(LOG_WARN, "[%s] RTP collision on video mountpoint, dropping packet (ssrc=%"SCNu32")\n",
							name, ssrc);
						continue;
					}
					source->last_received_video = now;
					//~ JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the video channel...\n", bytes);
					/* Is this SRTP? */
					if(source->is_srtp) {
						int buflen = bytes;
						srtp_err_status_t res = srtp_unprotect(source->srtp_ctx, buffer, &buflen);
						//~ if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
						if(res != srtp_err_status_ok) {
							guint32 timestamp = ntohl(rtp->timestamp);
							guint16 seq = ntohs(rtp->seq_number);
							JANUS_LOG(LOG_ERR, "[%s] Video SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
								name, janus_srtp_error_str(res), bytes, buflen, timestamp, seq);
							continue;
						}
						bytes = buflen;
					}
					/* First of all, let's check if this is (part of) a keyframe that we may need to save it for future reference */
					if(source->keyframe.enabled) {
						if(source->keyframe.temp_ts > 0 && ntohl(rtp->timestamp) != source->keyframe.temp_ts) {
							/* We received the last part of the keyframe, get rid of the old one and use this from now on */
							JANUS_LOG(LOG_HUGE, "[%s] ... ... last part of keyframe received! ts=%"SCNu32", %d packets\n",
								name, source->keyframe.temp_ts, g_list_length(source->keyframe.temp_keyframe));
							source->keyframe.temp_ts = 0;
							janus_mutex_lock(&source->keyframe.mutex);
							if(source->keyframe.latest_keyframe != NULL)
								g_list_free_full(source->keyframe.latest_keyframe, (GDestroyNotify)janus_streaming_rtp_relay_packet_free);
							source->keyframe.latest_keyframe = source->keyframe.temp_keyframe;
							source->keyframe.temp_keyframe = NULL;
							janus_mutex_unlock(&source->keyframe.mutex);
						} else if(ntohl(rtp->timestamp) == source->keyframe.temp_ts) {
							/* Part of the keyframe we're currently saving, store */
							janus_mutex_lock(&source->keyframe.mutex);
							JANUS_LOG(LOG_HUGE, "[%s] ... other part of keyframe received! ts=%"SCNu32"\n", name, source->keyframe.temp_ts);
							janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
							pkt->data = g_malloc(bytes);
							memcpy(pkt->data, buffer, bytes);
							pkt->data->ssrc = htons(1);
							pkt->data->type = mountpoint->codecs.video_pt;
							packet.is_rtp = TRUE;
							packet.is_video = TRUE;
							packet.is_keyframe = TRUE;
							pkt->length = bytes;
							pkt->timestamp = source->keyframe.temp_ts;
							pkt->seq_number = ntohs(rtp->seq_number);
							source->keyframe.temp_keyframe = g_list_append(source->keyframe.temp_keyframe, pkt);
							janus_mutex_unlock(&source->keyframe.mutex);
						} else {
							gboolean kf = FALSE;
							/* Parse RTP header first */
							janus_rtp_header *header = (janus_rtp_header *)buffer;
							guint32 timestamp = ntohl(header->timestamp);
							guint16 seq = ntohs(header->seq_number);
							JANUS_LOG(LOG_HUGE, "Checking if packet (size=%d, seq=%"SCNu16", ts=%"SCNu32") is a key frame...\n",
								bytes, seq, timestamp);
							int plen = 0;
							char *payload = janus_rtp_payload(buffer, bytes, &plen);
							if(payload) {
								switch(mountpoint->codecs.video_codec) {
									case JANUS_VIDEOCODEC_VP8:
										kf = janus_vp8_is_keyframe(payload, plen);
										break;
									case JANUS_VIDEOCODEC_VP9:
										kf = janus_vp9_is_keyframe(payload, plen);
										break;
									case JANUS_VIDEOCODEC_H264:
										kf = janus_h264_is_keyframe(payload, plen);
										break;
									default:
										break;
								}
								if(kf) {
									/* New keyframe, start saving it */
									source->keyframe.temp_ts = ntohl(rtp->timestamp);
									JANUS_LOG(LOG_HUGE, "[%s] New keyframe received! ts=%"SCNu32"\n", name, source->keyframe.temp_ts);
									janus_mutex_lock(&source->keyframe.mutex);
									janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
									pkt->data = g_malloc(bytes);
									memcpy(pkt->data, buffer, bytes);
									pkt->data->ssrc = htons(1);
									pkt->data->type = mountpoint->codecs.video_pt;
									packet.is_rtp = TRUE;
									packet.is_video = TRUE;
									packet.is_keyframe = TRUE;
									pkt->length = bytes;
									pkt->timestamp = source->keyframe.temp_ts;
									pkt->seq_number = ntohs(rtp->seq_number);
									source->keyframe.temp_keyframe = g_list_append(source->keyframe.temp_keyframe, pkt);
									janus_mutex_unlock(&source->keyframe.mutex);
								}
							}
						}
					}
					/* If paused, ignore this packet */
					if(!mountpoint->enabled)
						continue;
					//~ JANUS_LOG(LOG_VERB, " ... parsed RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n",
						//~ ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp));
					/* Relay on all sessions */
					packet.data = rtp;
					packet.length = bytes;
					packet.is_rtp = TRUE;
					packet.is_video = TRUE;
					packet.is_keyframe = FALSE;
					packet.simulcast = source->simulcast;
					packet.substream = index;
					packet.codec = mountpoint->codecs.video_codec;
					packet.svc = FALSE;
					if(source->svc) {
						/* We're doing SVC: let's parse this packet to see which layers are there */
						int plen = 0;
						char *payload = janus_rtp_payload(buffer, bytes, &plen);
						if(payload) {
							uint8_t pbit = 0, dbit = 0, ubit = 0, bbit = 0, ebit = 0;
							int found = 0, spatial_layer = 0, temporal_layer = 0;
							if(janus_vp9_parse_svc(payload, plen, &found, &spatial_layer, &temporal_layer, &pbit, &dbit, &ubit, &bbit, &ebit) == 0) {
								if(found) {
									packet.svc = TRUE;
									packet.spatial_layer = spatial_layer;
									packet.temporal_layer = temporal_layer;
									packet.pbit = pbit;
									packet.dbit = dbit;
									packet.ubit = ubit;
									packet.bbit = bbit;
									packet.ebit = ebit;
								}
							}
						}
					}
					/* Do we have a new stream? */
					if(ssrc != v_last_ssrc[index]) {
						v_last_ssrc[index] = ssrc;
						if(index == 0)
							source->video_ssrc = ssrc;
						JANUS_LOG(LOG_INFO, "[%s] New video stream! (ssrc=%"SCNu32", index %d)\n",
							name, v_last_ssrc[index], index);
					}
					packet.data->type = mountpoint->codecs.video_pt;
					/* Is there a recorder? (FIXME notice we only record the first substream, if simulcasting) */
					janus_rtp_header_update(packet.data, &source->context[index], TRUE, 0);
					if(source->vskew) {
						int ret = janus_rtp_skew_compensate_video(packet.data, &source->context[index], now);
						if(ret < 0) {
							JANUS_LOG(LOG_WARN, "[%s] Dropping %d packets, video source clock is too fast (ssrc=%"SCNu32", index %d)\n",
								name, -ret, v_last_ssrc[index], index);
							continue;
						} else if(ret > 0) {
							JANUS_LOG(LOG_WARN, "[%s] Jumping %d RTP sequence numbers, video source clock is too slow (ssrc=%"SCNu32", index %d)\n",
								name, ret, v_last_ssrc[index], index);
						}
					}
					if(index == 0) {
						packet.data->ssrc = htonl((uint32_t)mountpoint->id);
						janus_recorder_save_frame(source->vrc, buffer, bytes);
						packet.data->ssrc = htonl(ssrc);
					}
					/* Backup the actual timestamp and sequence number set by the restreamer, in case switching is involved */
					packet.timestamp = ntohl(packet.data->timestamp);
					packet.seq_number = ntohs(packet.data->seq_number);
					/* Take note of the simulcast SSRCs */
					if(source->simulcast) {
						packet.ssrc[0] = v_last_ssrc[0];
						packet.ssrc[1] = v_last_ssrc[1];
						packet.ssrc[2] = v_last_ssrc[2];
					}
					/* Go! */
					janus_mutex_lock(&mountpoint->mutex);
					g_list_foreach(mountpoint->helper_threads == 0 ? mountpoint->viewers : mountpoint->threads,
						mountpoint->helper_threads == 0 ? janus_streaming_relay_rtp_packet : janus_streaming_helper_rtprtcp_packet,
						&packet);
					janus_mutex_unlock(&mountpoint->mutex);
					continue;
				} else if(data_fd != -1 && fds[i].fd == data_fd) {
					/* Got something data (text) */
					if(mountpoint->active == FALSE)
						mountpoint->active = TRUE;
					source->last_received_data = janus_get_monotonic_time();
#ifdef HAVE_LIBCURL
					source->reconnect_timer = janus_get_monotonic_time();
#endif
					addrlen = sizeof(remote);
					bytes = recvfrom(data_fd, buffer, 1500, 0, &remote, &addrlen);
					if(bytes < 1) {
						/* Failed to read? */
						continue;
					}
					/* Get a string out of the data */
					char *text = g_malloc(bytes+1);
					memcpy(text, buffer, bytes);
					*(text+bytes) = '\0';
					/* Relay on all sessions */
					packet.data = (janus_rtp_header *)text;
					packet.length = bytes+1;
					packet.is_rtp = FALSE;
					/* Is there a recorder? */
					janus_recorder_save_frame(source->drc, text, strlen(text));
					/* Are we keeping track of the last message being relayed? */
					if(source->buffermsg) {
						janus_mutex_lock(&source->buffermsg_mutex);
						janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
						pkt->data = g_malloc(bytes+1);
						memcpy(pkt->data, text, bytes+1);
						packet.is_rtp = FALSE;
						pkt->length = bytes+1;
						janus_mutex_unlock(&source->buffermsg_mutex);
					}
					/* Go! */
					janus_mutex_lock(&mountpoint->mutex);
					g_list_foreach(mountpoint->helper_threads == 0 ? mountpoint->viewers : mountpoint->threads,
						mountpoint->helper_threads == 0 ? janus_streaming_relay_rtp_packet : janus_streaming_helper_rtprtcp_packet,
						&packet);
					janus_mutex_unlock(&mountpoint->mutex);
					packet.data = NULL;
					g_free(text);
					continue;
				} else if(audio_rtcp_fd != -1 && fds[i].fd == audio_rtcp_fd) {
					addrlen = sizeof(remote);
					bytes = recvfrom(audio_rtcp_fd, buffer, 1500, 0, &remote, &addrlen);
					if(bytes < 0 || (!janus_is_rtp(buffer, bytes) && !janus_is_rtcp(buffer, bytes))) {
						/* For latching we need an RTP or RTCP packet */
						continue;
					}
					memcpy(&source->audio_rtcp_addr, &remote, addrlen);
					if(!janus_is_rtcp(buffer, bytes)) {
						/* Failed to read or not an RTCP packet? */
						continue;
					}
					JANUS_LOG(LOG_HUGE, "[%s] Got audio RTCP feedback: SSRC %"SCNu32"\n",
						name, janus_rtcp_get_sender_ssrc(buffer, bytes));
					/* Relay on all sessions */
					packet.is_video = FALSE;
					packet.data = (janus_rtp_header *)buffer;
					packet.length = bytes;
					/* Go! */
					janus_mutex_lock(&mountpoint->mutex);
					g_list_foreach(mountpoint->helper_threads == 0 ? mountpoint->viewers : mountpoint->threads,
						mountpoint->helper_threads == 0 ? janus_streaming_relay_rtcp_packet : janus_streaming_helper_rtprtcp_packet,
						&packet);
					janus_mutex_unlock(&mountpoint->mutex);
				} else if(video_rtcp_fd != -1 && fds[i].fd == video_rtcp_fd) {
					addrlen = sizeof(remote);
					bytes = recvfrom(video_rtcp_fd, buffer, 1500, 0, &remote, &addrlen);
					if(bytes < 0 || (!janus_is_rtp(buffer, bytes) && !janus_is_rtcp(buffer, bytes))) {
						/* For latching we need an RTP or RTCP packet */
						continue;
					}
					memcpy(&source->video_rtcp_addr, &remote, addrlen);
					if(!janus_is_rtcp(buffer, bytes)) {
						/* Failed to read or not an RTCP packet? */
						continue;
					}
					JANUS_LOG(LOG_HUGE, "[%s] Got video RTCP feedback: SSRC %"SCNu32"\n",
						name, janus_rtcp_get_sender_ssrc(buffer, bytes));
					/* Relay on all sessions */
					packet.is_video = TRUE;
					packet.data = (janus_rtp_header *)buffer;
					packet.length = bytes;
					/* Go! */
					janus_mutex_lock(&mountpoint->mutex);
					g_list_foreach(mountpoint->helper_threads == 0 ? mountpoint->viewers : mountpoint->threads,
						mountpoint->helper_threads == 0 ? janus_streaming_relay_rtcp_packet : janus_streaming_helper_rtprtcp_packet,
						&packet);
					janus_mutex_unlock(&mountpoint->mutex);
				}
			}
		}
	}

	/* Notify users this mountpoint is done */
	janus_mutex_lock(&mountpoint->mutex);
	GList *viewer = g_list_first(mountpoint->viewers);
	/* Prepare JSON event */
	json_t *event = json_object();
	json_object_set_new(event, "streaming", json_string("event"));
	json_t *result = json_object();
	json_object_set_new(result, "status", json_string("stopped"));
	json_object_set_new(event, "result", result);
	while(viewer) {
		janus_streaming_session *session = (janus_streaming_session *)viewer->data;
		if(session != NULL) {
			session->stopping = TRUE;
			session->started = FALSE;
			session->paused = FALSE;
			session->mountpoint = NULL;
			/* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
			gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
			gateway->close_pc(session->handle);
			janus_refcount_decrease(&session->ref);
			janus_refcount_decrease(&mountpoint->ref);
		}
		mountpoint->viewers = g_list_remove_all(mountpoint->viewers, session);
		viewer = g_list_first(mountpoint->viewers);
	}
	json_decref(event);
	janus_mutex_unlock(&mountpoint->mutex);

	JANUS_LOG(LOG_VERB, "[%s] Leaving streaming relay thread\n", name);
	g_free(name);
	janus_refcount_decrease(&mountpoint->ref);
	return NULL;
}

static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) {
	janus_streaming_rtp_relay_packet *packet = (janus_streaming_rtp_relay_packet *)user_data;
	if(!packet || !packet->data || packet->length < 1) {
		JANUS_LOG(LOG_ERR, "Invalid packet...\n");
		return;
	}
	janus_streaming_session *session = (janus_streaming_session *)data;
	if(!session || !session->handle) {
		//~ JANUS_LOG(LOG_ERR, "Invalid session...\n");
		return;
	}
	if(!packet->is_keyframe && (!session->started || session->paused)) {
		//~ JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
		return;
	}

	if(packet->is_rtp) {
		/* Make sure there hasn't been a video source switch by checking the SSRC */
		if(packet->is_video) {
			if(!session->video)
				return;
			/* Check if there's any SVC info to take into account */
			if(packet->svc) {
				/* There is: check if this is a layer that can be dropped for this viewer
				 * Note: Following core inspired by the excellent job done by Sergio Garcia Murillo here:
				 * https://github.com/medooze/media-server/blob/master/src/vp9/VP9LayerSelector.cpp */
				gboolean override_mark_bit = FALSE, has_marker_bit = packet->data->markerbit;
				int temporal_layer = session->temporal_layer;
				if(session->target_temporal_layer > session->temporal_layer) {
					/* We need to upscale */
					JANUS_LOG(LOG_HUGE, "We need to upscale temporally:\n");
					if(packet->ubit && packet->bbit && packet->temporal_layer <= session->target_temporal_layer) {
						JANUS_LOG(LOG_HUGE, "  -- Upscaling temporal layer: %u --> %u\n",
							packet->temporal_layer, session->target_temporal_layer);
						session->temporal_layer = packet->temporal_layer;
						temporal_layer = session->temporal_layer;
						/* Notify the viewer */
						json_t *event = json_object();
						json_object_set_new(event, "streaming", json_string("event"));
						json_t *result = json_object();
						json_object_set_new(result, "temporal_layer", json_integer(session->temporal_layer));
						json_object_set_new(event, "result", result);
						gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
						json_decref(event);
					}
				} else if(session->target_temporal_layer < session->temporal_layer) {
					/* We need to downscale */
					JANUS_LOG(LOG_HUGE, "We need to downscale temporally:\n");
					if(packet->ebit) {
						JANUS_LOG(LOG_HUGE, "  -- Downscaling temporal layer: %u --> %u\n",
							session->temporal_layer, session->target_temporal_layer);
						session->temporal_layer = session->target_temporal_layer;
						/* Notify the viewer */
						json_t *event = json_object();
						json_object_set_new(event, "streaming", json_string("event"));
						json_t *result = json_object();
						json_object_set_new(result, "temporal_layer", json_integer(session->temporal_layer));
						json_object_set_new(event, "result", result);
						gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
						json_decref(event);
					}
				}
				if(temporal_layer < packet->temporal_layer) {
					/* Drop the packet: update the context to make sure sequence number is increased normally later */
					JANUS_LOG(LOG_HUGE, "Dropping packet (temporal layer %d < %d)\n", temporal_layer, packet->temporal_layer);
					session->context.v_base_seq++;
					return;
				}
				int spatial_layer = session->spatial_layer;
				if(session->target_spatial_layer > session->spatial_layer) {
					JANUS_LOG(LOG_HUGE, "We need to upscale spatially:\n");
					/* We need to upscale */
					if(packet->pbit == 0 && packet->bbit && packet->spatial_layer == session->spatial_layer+1) {
						JANUS_LOG(LOG_HUGE, "  -- Upscaling spatial layer: %u --> %u\n",
							packet->spatial_layer, session->target_spatial_layer);
						session->spatial_layer = packet->spatial_layer;
						spatial_layer = session->spatial_layer;
						/* Notify the viewer */
						json_t *event = json_object();
						json_object_set_new(event, "streaming", json_string("event"));
						json_t *result = json_object();
						json_object_set_new(result, "spatial_layer", json_integer(session->spatial_layer));
						json_object_set_new(event, "result", result);
						gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
						json_decref(event);
					}
				} else if(session->target_spatial_layer < session->spatial_layer) {
					/* We need to downscale */
					JANUS_LOG(LOG_HUGE, "We need to downscale spatially:\n");
					if(packet->ebit) {
						JANUS_LOG(LOG_HUGE, "  -- Downscaling spatial layer: %u --> %u\n",
							session->spatial_layer, session->target_spatial_layer);
						session->spatial_layer = session->target_spatial_layer;
						/* Notify the viewer */
						json_t *event = json_object();
						json_object_set_new(event, "streaming", json_string("event"));
						json_t *result = json_object();
						json_object_set_new(result, "spatial_layer", json_integer(session->spatial_layer));
						json_object_set_new(event, "result", result);
						gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
						json_decref(event);
					}
				}
				if(spatial_layer < packet->spatial_layer) {
					/* Drop the packet: update the context to make sure sequence number is increased normally later */
					JANUS_LOG(LOG_HUGE, "Dropping packet (spatial layer %d < %d)\n", spatial_layer, packet->spatial_layer);
					session->context.v_base_seq++;
					return;
				} else if(packet->ebit && spatial_layer == packet->spatial_layer) {
					/* If we stop at layer 0, we need a marker bit now, as the one from layer 1 will not be received */
					override_mark_bit = TRUE;
				}
				/* If we got here, we can send the frame: this doesn't necessarily mean it's
				 * one of the layers the user wants, as there may be dependencies involved */
				JANUS_LOG(LOG_HUGE, "Sending packet (spatial=%d, temporal=%d)\n",
					packet->spatial_layer, packet->temporal_layer);
				/* Fix sequence number and timestamp (video source switching may be involved) */
				janus_rtp_header_update(packet->data, &session->context, TRUE, 4500);
				if(override_mark_bit && !has_marker_bit) {
					packet->data->markerbit = 1;
				}
				if(gateway != NULL)
					gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
				if(override_mark_bit && !has_marker_bit) {
					packet->data->markerbit = 0;
				}
				/* Restore the timestamp and sequence number to what the video source set them to */
				packet->data->timestamp = htonl(packet->timestamp);
				packet->data->seq_number = htons(packet->seq_number);
			} else if(packet->simulcast) {
				/* Handle simulcast: don't relay if it's not the substream we wanted to handle */
				int plen = 0;
				char *payload = janus_rtp_payload((char *)packet->data, packet->length, &plen);
				if(payload == NULL)
					return;
				/* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */
				gboolean relay = janus_rtp_simulcasting_context_process_rtp(&session->sim_context,
					(char *)packet->data, packet->length, packet->ssrc, NULL, packet->codec, &session->context);
				/* Do we need to drop this? */
				if(!relay)
					return;
				/* Any event we should notify? */
				if(session->sim_context.changed_substream) {
					/* Notify the user about the substream change */
					json_t *event = json_object();
					json_object_set_new(event, "streaming", json_string("event"));
					json_t *result = json_object();
					json_object_set_new(result, "substream", json_integer(session->sim_context.substream));
					json_object_set_new(event, "result", result);
					gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
					json_decref(event);
				}
				if(session->sim_context.need_pli) {
					/* Schedule a PLI */
					JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n");
					if(session->mountpoint != NULL) {
						janus_streaming_rtp_source *source = session->mountpoint->source;
						if(source != NULL)
							g_atomic_int_set(&source->need_pli, 1);
					}
				}
				if(session->sim_context.changed_temporal) {
					/* Notify the user about the temporal layer change */
					json_t *event = json_object();
					json_object_set_new(event, "streaming", json_string("event"));
					json_t *result = json_object();
					json_object_set_new(result, "temporal", json_integer(session->sim_context.templayer));
					json_object_set_new(event, "result", result);
					gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
					json_decref(event);
				}
				/* If we got here, update the RTP header and send the packet */
				janus_rtp_header_update(packet->data, &session->context, TRUE, 4500);
				char vp8pd[6];
				if(packet->codec == JANUS_VIDEOCODEC_VP8) {
					/* For VP8, we save the original payload descriptor, to restore it after */
					memcpy(vp8pd, payload, sizeof(vp8pd));
					janus_vp8_simulcast_descriptor_update(payload, plen, &session->vp8_context,
						session->sim_context.changed_substream);
				}
				/* Send the packet */
				if(gateway != NULL)
					gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
				/* Restore the timestamp and sequence number to what the publisher set them to */
				packet->data->timestamp = htonl(packet->timestamp);
				packet->data->seq_number = htons(packet->seq_number);
				if(packet->codec == JANUS_VIDEOCODEC_VP8) {
					/* Restore the original payload descriptor as well, as it will be needed by the next viewer */
					memcpy(payload, vp8pd, sizeof(vp8pd));
				}
			} else {
				/* Fix sequence number and timestamp (switching may be involved) */
				janus_rtp_header_update(packet->data, &session->context, TRUE, 0);
				if(gateway != NULL)
					gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
				/* Restore the timestamp and sequence number to what the video source set them to */
				packet->data->timestamp = htonl(packet->timestamp);
				packet->data->seq_number = htons(packet->seq_number);
			}
		} else {
			if(!session->audio)
				return;
			/* Fix sequence number and timestamp (switching may be involved) */
			janus_rtp_header_update(packet->data, &session->context, FALSE, 0);
			if(gateway != NULL)
				gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
			/* Restore the timestamp and sequence number to what the video source set them to */
			packet->data->timestamp = htonl(packet->timestamp);
			packet->data->seq_number = htons(packet->seq_number);
		}
	} else {
		/* We're broadcasting a data channel message */
		if(!session->data)
			return;
		char *text = (char *)packet->data;
		if(gateway != NULL && text != NULL)
			gateway->relay_data(session->handle, NULL, text, strlen(text));
	}

	return;
}


static void janus_streaming_relay_rtcp_packet(gpointer data, gpointer user_data) {
	janus_streaming_rtp_relay_packet *packet = (janus_streaming_rtp_relay_packet *)user_data;
	if(!packet || !packet->data || packet->length < 1) {
		JANUS_LOG(LOG_ERR, "Invalid packet...\n");
		return;
	}
	janus_streaming_session *session = (janus_streaming_session *)data;
	if(!session || !session->handle) {
		//~ JANUS_LOG(LOG_ERR, "Invalid session...\n");
		return;
	}
	if(!session->started || session->paused) {
		//~ JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
		return;
	}

	if(gateway != NULL)
		gateway->relay_rtcp(session->handle, (packet->is_video ? 1 : 0), (char*)packet->data, packet->length);

	return;
}

static void janus_streaming_helper_rtprtcp_packet(gpointer data, gpointer user_data) {
	janus_streaming_rtp_relay_packet *packet = (janus_streaming_rtp_relay_packet *)user_data;
	if(!packet || !packet->data || packet->length < 1) {
		JANUS_LOG(LOG_ERR, "Invalid packet...\n");
		return;
	}
	janus_streaming_helper *helper = (janus_streaming_helper *)data;
	if(!helper) {
		//~ JANUS_LOG(LOG_ERR, "Invalid session...\n");
		return;
	}
	/* Clone the packet and queue it for delivery on the helper thread */
	janus_streaming_rtp_relay_packet *copy = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
	copy->data = g_malloc(packet->length);
	memcpy(copy->data, packet->data, packet->length);
	copy->length = packet->length;
	copy->is_rtp = packet->is_rtp;
	copy->is_video = packet->is_video;
	copy->is_keyframe = packet->is_keyframe;
	copy->simulcast = packet->simulcast;
	copy->ssrc[0] = packet->ssrc[0];
	copy->ssrc[1] = packet->ssrc[1];
	copy->ssrc[2] = packet->ssrc[2];
	copy->codec = packet->codec;
	copy->substream = packet->substream;
	copy->timestamp = packet->timestamp;
	copy->seq_number = packet->seq_number;
	g_async_queue_push(helper->queued_packets, copy);
}

static void *janus_streaming_helper_thread(void *data) {
	janus_streaming_helper *helper = (janus_streaming_helper *)data;
	janus_streaming_mountpoint *mp = helper->mp;
	JANUS_LOG(LOG_INFO, "[%s/#%d] Joining Streaming helper thread\n", mp->name, helper->id);
	janus_streaming_rtp_relay_packet *pkt = NULL;
	while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&mp->destroyed)) {
		pkt = g_async_queue_pop(helper->queued_packets);
		if(pkt == NULL)
			continue;
		if(pkt == &exit_packet)
			break;
		janus_mutex_lock(&helper->mutex);
		g_list_foreach(helper->viewers,
			pkt->is_rtp ? janus_streaming_relay_rtp_packet : janus_streaming_relay_rtcp_packet,
			pkt);
		janus_mutex_unlock(&helper->mutex);
		janus_streaming_rtp_relay_packet_free(pkt);
	}
	JANUS_LOG(LOG_INFO, "[%s/#%d] Leaving Streaming helper thread\n", mp->name, helper->id);
	janus_mutex_lock(&mp->mutex);
	janus_mutex_lock(&helper->mutex);
	g_async_queue_unref(helper->queued_packets);
	if(helper->viewers != NULL)
		g_list_free(helper->viewers);
	janus_mutex_unlock(&helper->mutex);
	g_free(helper);
	janus_mutex_unlock(&mp->mutex);
	janus_refcount_decrease(&mp->ref);
	return NULL;
}
