/*! \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 Opus, 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 few 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 some examples you can start from.
 *
 * 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. Notice that, for the sake of
 * simplicity, only the global and legacy properties are listed in the
 * following text: you can use the new stream-based syntax as well (see
 * the 'multistream-test' example below for reference), but in that case
 * all properties whose names start with audio/video/data should be
 * renamed to have that prefix removed. Please refer to the plugin API
 * documentation for more details, as the static configuration in that
 * case mirrors the dynamic API syntax.
 *
 * \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
metadata = An optional string that can contain any metadata (e.g., JSON)
			associated with the stream you want users to receive
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)
audiocodec = name of the audio codec (opus)
audiofmtp = Codec specific parameters, if any
audioskew = true|false (whether the plugin should perform skew
	analysis 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)
videocodec = name of the video codec (vp8)
videofmtp = Codec specific parameters, if any
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
	analysis 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)
h264sps = if using H.264 as a video codec, value of the sprop-parameter-sets
	that would normally be sent via SDP, but that we'll use to instead
	manually ingest SPS and PPS packets via RTP for streams that miss it
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
datamcast = multicast group for receiving data messages, if any
dataiface = network interface or IP address to bind to, if any (binds to all otherwise)
datatype = text|binary (type of data this mountpoint will relay, default=text)
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)

Note: by default, the Streaming plugin only forwards the latest packets
it receives, never performing any buffering. This means that, for video
streams, new viewers may initially start receiving frames they cannot
decode right away, since they'd refer to keyframes that were sent before
they joined. In such scenarios, they'd have to wait until the next keyframe
arrives before video can be decoded and displayed, which could take a
while depending on the frequency of keyframes encoded by the source.
For forwarded streams (e.g., from the VideoRoom) this can be easily
addressed by using the RTCP support. For sources that can't or won't
honour dynamic keyframe requests, a partial and experimental solution
might be storing the latest keyframe and the following deltas, to send
to new viewers before new live packet are delivered. This feature can
be enabled in the Streaming plugin using the 'bufferkf_ms' and/or
the 'bufferkf_bytes' properties, which configure how many milliseconds
or how many bytes (in total) should be stored any time a keyframe is
received: data exceeding those limits won't be stored, until a new
keyframe arrives. The two properties are not mutually exclusive, and
can be configured at the same time: in that case, the first one that
hits the limit stops the buffering of the current keyframe. Notice
that, again, this feature should be considered highly experimental,
and that it comes with a few considerable drawbacks: the most obvious
one is that, depending on how many packets were stored, new viewers
may be hit with a considerable burst of data as soon as they connect,
which may negatively impact performance or even cause issues of its own.
Also notice that this is a global, and not per-stream, feature: this
means that if you create a mountpoint with two video streams, the
feature will impact both of them, and both streams will have a buffer
of their own. This also works for RTSP mountpoints.
	bufferkf_ms = how many milliseconds of packets to store, starting
		from a new keyframe (default=0)
	bufferkf_bytes = how many bytes of packets to store, starting
		from a new keyframe (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 Streaming plugin can also be used to (re)stream media that has been
encrypted using something that can be consumed via Insertable Streams.
In that case, we only need to be aware of it, so that we can send the
info along with the SDP. How to decrypt the media is out of scope, and
up to the application since, again, this is end-to-end encryption and
so neither Janus nor the Streaming plugin have access to anything.
DO NOT SET THIS PROPERTY IF YOU DON'T KNOW WHAT YOU'RE DOING!
e2ee = true

To allow mountpoints to negotiate the playout-delay RTP extension,
you can set the 'playoutdelay_ext' property to true: this way, any
subscriber can customize the playout delay of incoming video streams,
assuming the browser supports the RTP extension in the first place.
playoutdelay_ext = true

To allow mountpoints to negotiate the abs-capture-time RTP extension,
you can set the 'abscapturetime_src_ext_id' property to value in range 1..14 inclusive: this way, any
subscriber can receive the abs-capture-time of incoming RTP streams,
assuming the browser supports the RTP extension in the first place.
Incoming RTP stream should provide abs-capture-time exactly in the same header id.
abscapturetime_src_ext_id = 1

The following options are only valid for the 'rtsp' type:
url = RTSP stream URL
rtsp_user = RTSP authorization username, if needed
rtsp_pwd = RTSP authorization password, if needed
rtsp_quirk = Some RTSP servers offer the stream using only the path, instead of the fully qualified URL.
	If set true, this boolean informs Janus that we should try a path-only DESCRIBE request if the initial request returns 404.
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
rtsp_reconnect_delay = after n seconds passed and no media assumed, the RTSP server has gone and schedule a reconnect (default=5s)
rtsp_session_timeout = by default the streaming plugin will check the RTSP connection with an OPTIONS query,
	the value of the timeout comes from the RTSP session initializer and by default
	this session timeout is the half of this value In some cases this value can be too high (for example more than one minute)
	because of the media server. In that case this plugin will calculate the timeout with this
	formula: timeout = min(session_timeout, rtsp_session_timeout / 2). (default=0s)
rtsp_timeout = communication timeout (CURLOPT_TIMEOUT) for cURL call gathering the RTSP information (default=10s)
rtsp_conn_timeout = connection timeout for cURL (CURLOPT_CONNECTTIMEOUT) call gathering the RTSP information (default=5s)
rtsp_notify_changes = if set to true, will send an event to connected users when the RTSP session
	gets disconnected, and when it's reconnected (default=false)
\endverbatim
 *
 * Notice that attributes like \c audioport or \c videopt only make sense
 * when you're creating a mountpoint with a single audio and/or video stream,
 * as the plugin in that case assumes that limitation is fine by you. In
 * case you're interested in creating multistream mountpoints, that is
 * mountpoints that can contain more than one audio and/or video stream
 * at the same time, you HAVE to use a different syntax. Specifically,
 * you'll need to use a \c media array/list, containing the different
 * streams, in the right order, that you want to make available: each
 * stream will then need to contain the related info, e.g., port to bind
 * to, type of media, codec name and so on. An example is provided below:
 *
\verbatim
multistream-test: {
	type = "rtp"
	id = 123
	description = "Multistream test (1 audio, 2 video)"
	media = (
		{
			type = "audio"
			mid = "a"
			label = "Audio stream"
			port = 5102
			pt = 111
			codec = "opus"
		},
		{
			type = "video"
			mid = "v1"
			label = "Video stream #1"
			port = 5104
			pt = 100
			codec = "vp8"
		},
		{
			type = "video"
			mid = "v2"
			label = "Video stream #2"
			port = 5106
			pt = 100
			codec = "vp8"
		}
	)
}
\endverbatim
 *
 * In the above example, we're creating a mountpoint with a single audio
 * stream and two different video streams: each stream has a unique \c mid
 * (that you MUST provide) which is what will be used for the SDP offer
 * to send to viewers, and their unique configuration properties. As you
 * can see, it's much cleaner in the way you create and configure
 * mountpoints: there's no hardcoded audio/video prefix for the name of
 * properties, you configure media streams the same way and just add them
 * to a list. Notice that of course this also works with the simple one
 * audio/one video mountpoints you've used so far, and that has been
 * documented before: as such, you're encouraged to start using this
 * new approach as soon as possible, since in the next versions we
 * might deprecate the old one.
 *
 * \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>,
			"type" : "<type of mountpoint #1, in line with the types introduced above>",
			"description" : "<description of mountpoint #1>",
			"metadata" : "<metadata of mountpoint #1, if any>",
			"enabled" : <true|false, depending on whether the mountpoint is currently enabled or not>,
			"media" : [
				{
					"mid" : "<unique mid of this stream>",
					"label" : "<unique text label of this stream>",
					"msid" : "<msid of this stream, if configured>",
					"type" : "<audio|video|data">,
					"age_ms" : <how much time passed since we last received media for this stream; optional>,
				},
				{
					// Other streams, if available
				}
			]
		},
		{
			"id" : <unique ID of mountpoint #2>,
			"type" : "<type of mountpoint #2, in line with the types introduced above>",
			"description" : "<description of mountpoint #2>",
			"metadata" : "<metadata of mountpoint #2, if any>",
			"media" : [..]
		},
		...
	]
}
\endverbatim
 *
 * As you can see, the \c list request only returns very generic info on
 * each mountpoint. 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>",
		"metadata" : "<metadata of mountpoint, if any>",
		"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>,
		"viewers" : <count of current subscribers, if any>,
		"enabled" : <true|false, depending on whether the mountpoint is currently enabled or not>,
		"type" : "<type of mountpoint>",
		"media" : [
			{
				"mid" : "<unique mid of this stream>",
				"mindex" : "<unique mindex of this stream>",
				"type" : "<audio|video|data">,
				"label" : "<unique text label of this stream>",
				"msid" : "<msid of this stream, if configured>",
				"age_ms" : <how much time passed since we last received media for this stream; optional>,
				"pt" : <payload type, only present if RTP and configured>,
				"codec" : "<cocec name value, only present if RTP and configured>",
				"rtpmap" : "<SDP rtpmap value, only present if RTP and configured>",
				"fmtp" : "<audio SDP fmtp value, only present if RTP and configured>",
				...
			},
			{
				// Other streams, if available
			}
		]
	}
}
\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>",
	"metadata" : "<metadata 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>,
	"media" : [
		{
			"type" : "<audio|video|data>",
			"mid" : "<unique mid to assign to this stream in negotiated PeerConnections>",
			"msid" : "<msid to add to the m-line, if needed>",
			"label", "<optional label to name the track>",
			"mcast", "<multicast group for receiving packets, if any>",
			"iface", "<network interface or IP address to bind to, if any (binds to all otherwise)>",
			"port" : <port to bind to, to receive media to relay>",
			"rtcpport", <port to bind to for receiving and sending audio RTCP feedback, if any>,
			"pt", <RTP payload type (audio/video only)>,
			"codec", "<name of the codec that will be used for this track (audio/video only)>",
			"fmtp", "<codec specific parameters, if any (audio/video only)>",
			"skew", <true|false (whether the plugin should perform skew
				analysis and compensation on incoming RTP streams, EXPERIMENTAL)>
			"simulcast", <true|false (do|don't enable video simulcasting)>,
			"port2", <second local port for receiving video frames (only for RTP video, and simulcasting)>,
			"port3", <third local port for receiving video frames (only for RTP video, and simulcasting)>,
			"svc", <true|false (whether the RTP video will have SVC support; works only for VP9-SVC, default=false)>,
			"datatype", "<text|binary (type of data this mountpoint will relay, default=text)>",
			...
		}.
		... other streams, if any ...
	],
	...
	"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 ).
 *
 * Notice that, just as we introduced the possibility of configuring multistream
 * mountpoints statically with a \c media array, the same applies when using
 * the API to create them: just add a \c media JSON array containing the
 * list of streams to create and the related properties as you would do
 * statically (that is, using generic properties like \c port, \c fmtp,
 * etc., rather than the hardcoded \c audioport and the like), and it
 * will work for dynamically created mountpoints as well.
 *
 * 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>,
		"ports" : [		// Only for RTP mountpoints
			{
				"type" : "<audio|video|data>",
				"mid" : "<unique mid of stream #1>",
				"msid" : "<msid of this stream, if configured>",
				"port" : <port the plugin is listening on for this stream's media>
			},
			{
				// Other streams, if available
			}
		]
		...
	}
}
\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 mountpoint 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_metadata" : "<new metadata for the mountpoint; optional>",
	"new_secret" : "<new secret for the mountpoint; optional>",
	"new_pin" : "<new PIN for the mountpoint, PIN will be removed if set to an empty string; 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>,
	"edited_event" : <true|false, whether an event will be sent to all viewers when metadata is updated; 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
 *
 * In case \c edited_event was set to \c true in the request, a successful \c edit will
 * also result in an \c edited event sent to all viewers when the metadata has changed:
 *
\verbatim
{
	"streaming" : "edited",
	"id" : <unique ID of the just edited mountpoint>,
	"metadata" : "<updated metadata for the mountpoint>",
}
\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",
	"id" : <unique ID of the mountpoint to enable; mandatory>,
	"secret" : "<secret to enable the mountpoint; mandatory if configured>"
}
\endverbatim
 *
 * If successful, a generic \c ok is returned:
 *
\verbatim
{
	"streaming" : "ok"
}
\endverbatim
\verbatim
{
	"request" : "disable",
	"id" : <unique ID of the mountpoint to disable; mandatory>,
	"stop_recording" : <true|false, whether the recording should also be stopped or not; true by default>
	"secret" : "<secret to disable the mountpoint; mandatory if configured>"
}
\endverbatim
 *
 * If successful, a generic \c ok is returned:
 *
\verbatim
{
	"streaming" : "ok"
}
\endverbatim
 *
 * You can kick all viewers from a mountpoint using the \c kick_all request. Notice
 * that this only removes all viewers, but does not prevent them from starting to watch
 * the mountpoint again. Please note this request works with all mountpoint types,
 * except for on-demand streaming. The \c kick_all request has to be formatted as follows:
 *
\verbatim
{
	"request" : "kick_all",
	"id" : <unique ID of the mountpoint to disable; mandatory>,
	"secret" : "<mountpoint secret; mandatory if configured>",
}
\endverbatim
 *
 * If successful, a \c kicked_all response is returned:
 *
\verbatim
{
	"streaming" : "kicked_all",
}
\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. Notice that, while for backwards compatibility
 * you can still use the old \c audio, \c video and \c data named properties,
 * they're now deprecated and so you're highly encouraged to use the new drill-down
 * \c media list instead.
 *
 * 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>,
	"media" : [		// Drill-down recording controls
		{
			"mid" : "<mid of the stream to start recording>",
			"filename" : "<base path/filename to use for the recording>"
		},
		{
			// Recording controls for other streams, if provided
		}
	]
}
\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>,
	"media" : [		// Drill-down recording controls
		{
			"mid" : "<mid of the stream to stop recording>"
		},
		{
			// Recording controls for other streams, if provided
		}
	]
}
\endverbatim
 *
 * When using the deprecated properties, when starting 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. This is one more reason why
 * you should migrate to the new \c media list instead, as it doesn't have
 * this ambiguity between the two different requests. 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>",
	"media" : [
		<array of mids to subscribe to, as strings; optional, missing or empty array subscribes to all mids>
	]
	"offer_audio" : <true|false; deprecated; whether or not audio should be negotiated; true by default if the mountpoint has audio>,
	"offer_video" : <true|false; deprecated; whether or not video should be negotiated; true by default if the mountpoint has video>,
	"offer_data" : <true|false; deprecated; 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 media array is particularly interesting, as it
 * allows you to only subscribe to a subset of the mountpoint media, which
 * you can address by the related \c mid property of each stream. 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. The \c media array is optional, as a missing
 * or empty array will simply be interpreted as a willingness to subscribe
 * to all the streams in the mountpoint, which is the default behaviour.
 * Notice that the order of the mids in the \c media array is irrelevant,
 * as is how many times the same mid is listed in the array: the presence
 * of a mid is just interpreted as an "on" switch for that stream, meaning
 * it will be offered in the SDP.
 *
 * \note For backwards compatibility, the deprecated \c offer_audio ,
 * \c offer_video and \c offer_data properties are also available. They
 * also allow you to only subscribe to a subset of the mountpoint media,
 * but with a more crude approach: specifically, they dictate whether or
 * not any audio, video or data stream should be offered or not.
 *
 * 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",
	"streams" : [
		{
			"mid" : <mid of the m-line to tweak>,
			"send" : <true|false, depending on whether the media addressed by the above mid 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>,
			"fallback" : <How much time (in us, default 250000) without receiving packets will make us drop to the substream below>,
			"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>,
			"min_delay" : <minimum delay to enforce via the playout-delay RTP extension, in blocks of 10ms; optional>,
			"max_delay" : <maximum delay to enforce via the playout-delay RTP extension, in blocks of 10ms; optional>
		},
		// Other streams, if any
	]
}
\endverbatim
 *
 * While the deprecated \c audio , \c video and \c data properties can still be
 * used as a media-level pause/resume functionality, a better option is to
 * specify the \c mid of the stream instead, and a \c send boolean property
 * to specify if this specific stream should be relayed or not. The \c pause
 * and \c start requests instead pause and resume all streams at the same time.
 * The \c substream and \c temporal properties, finally, 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. In both cases, make
 * sure you specify the \c mid of the stream in case multiple videos are
 * available in a mountpoint, or the request may have no effect.
 *
 * 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>
#ifndef CURL_AT_LEAST_VERSION
#define CURL_AT_LEAST_VERSION(x,y,z) 0
#endif
#endif

#ifdef HAVE_LIBOGG
#include <ogg/ogg.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 "../sdp-utils.h"
#include "../ip-utils.h"

/* Default settings */
#define JANUS_STREAMING_DEFAULT_SESSION_TIMEOUT 0 /* Overwrite the RTSP session timeout. If set to zero, the RTSP timeout is derived from a session. */
#define JANUS_STREAMING_DEFAULT_RECONNECT_DELAY 5 /* Reconnecting delay in seconds. */
#define JANUS_STREAMING_DEFAULT_CURL_TIMEOUT 10L /* Communication timeout for cURL. */
#define JANUS_STREAMING_DEFAULT_CURL_CONNECT_TIMEOUT 5L /* Connection timeout for cURL. */

/* Plugin information */
#define JANUS_STREAMING_VERSION			11
#define JANUS_STREAMING_VERSION_STRING	"0.0.11"
#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, janus_plugin_rtp *packet);
void janus_streaming_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet);
void janus_streaming_data_ready(janus_plugin_session *handle);
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,
		.data_ready = janus_streaming_data_ready,
		.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 idopt_parameters[] = {
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter idstr_parameters[] = {
	{"id", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter idstropt_parameters[] = {
	{"id", JSON_STRING, 0}
};
static struct janus_json_parameter watch_parameters[] = {
	{"pin", JSON_STRING, 0},
	{"media", JANUS_JSON_ARRAY, 0},
	{"restart", JANUS_JSON_BOOL, 0},
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"offer_audio", JANUS_JSON_BOOL, 0},
	{"offer_video", JANUS_JSON_BOOL, 0},
	{"offer_data", 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[] = {
	{"new_description", JSON_STRING, 0},
	{"new_metadata", 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},
	{"edited_event", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter create_parameters[] = {
	{"name", JSON_STRING, 0},
	{"description", JSON_STRING, 0},
	{"metadata", JSON_STRING, 0},
	{"is_private", JANUS_JSON_BOOL, 0},
	{"type", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"secret", JSON_STRING, 0},
	{"pin", JSON_STRING, 0},
	{"media", JANUS_JSON_ARRAY, 0},
	{"permanent", JANUS_JSON_BOOL, 0},
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"audio", JANUS_JSON_BOOL, 0},
	{"video", JANUS_JSON_BOOL, 0},
	{"data", JANUS_JSON_BOOL, 0},
};
static struct janus_json_parameter rtp_parameters[] = {
	{"collision", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"bufferkf_ms", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"bufferkf_bytes", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"threads", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"srtpsuite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"srtpcrypto", JSON_STRING, 0},
	{"e2ee", JANUS_JSON_BOOL, 0},
	{"playoutdelay_ext", JANUS_JSON_BOOL, 0},
	{"abscapturetime_src_ext_id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter live_parameters[] = {
	{"filename", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"audiocodec", JSON_STRING, 0},
	{"audiortpmap", JSON_STRING, 0},	/* Deprecated */
	{"audiofmtp", JSON_STRING, 0},
	{"audiopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter ondemand_parameters[] = {
	{"filename", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"audiocodec", JSON_STRING, 0},
	{"audiortpmap", JSON_STRING, 0},	/* Deprecated */
	{"audiofmtp", JSON_STRING, 0},
	{"audiopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
#ifdef HAVE_LIBCURL
static struct janus_json_parameter rtsp_parameters[] = {
	{"url", JSON_STRING, 0},
	{"rtsp_user", JSON_STRING, 0},
	{"rtsp_pwd", JSON_STRING, 0},
	{"rtsp_quirk", JANUS_JSON_BOOL, 0},
	{"rtsp_failcheck", JANUS_JSON_BOOL, 0},
	{"rtspiface", JSON_STRING, 0},
	{"rtsp_reconnect_delay", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"rtsp_session_timeout", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"rtsp_timeout", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"rtsp_conn_timeout", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"rtsp_notify_changes", JANUS_JSON_BOOL, 0},
	{"audiocodec", JSON_STRING, 0},
	{"audiortpmap", JSON_STRING, 0},	/* Deprecated */
	{"audiofmtp", JSON_STRING, 0},
	{"audiopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"videocodec", JSON_STRING, 0},
	{"videortpmap", JSON_STRING, 0},	/* Deprecated */
	{"videofmtp", JSON_STRING, 0},
	{"videopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"videobufferkf", JANUS_JSON_BOOL, 0},	/* Deprecated for the properties below */
	{"bufferkf_ms", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"bufferkf_bytes", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"threads", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
};
#endif
static struct janus_json_parameter rtp_media_parameters[] = {
	{"type", JANUS_JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"mid", JANUS_JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
	{"label", JANUS_JSON_STRING, 0},
	{"msid", JANUS_JSON_STRING, 0},
	{"mcast", JANUS_JSON_STRING, 0},
	{"iface", JANUS_JSON_STRING, 0},
	{"port", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"rtcpport", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"pt", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"codec", JANUS_JSON_STRING, 0},
	{"rtpmap", JANUS_JSON_STRING, 0},	/* Deprecated */
	{"fmtp", JANUS_JSON_STRING, 0},
	{"skew", JANUS_JSON_BOOL, 0},
	/* Video only */
	{"bufferkf", JANUS_JSON_BOOL, 0},	/* Deprecated: see global bufferkf_ms and bufferkf_bytes */
	{"simulcast", JANUS_JSON_BOOL, 0},
	{"port2", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"port3", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"svc", JANUS_JSON_BOOL, 0},
	/* Data only */
	{"datatype", JANUS_JSON_STRING, 0},
	{"buffermsg", JANUS_JSON_BOOL, 0},
};
static struct janus_json_parameter rtp_audio_parameters[] = {
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"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},
	{"audiocodec", JSON_STRING, 0},
	{"audiortpmap", JSON_STRING, 0},	/* Deprecated */
	{"audiofmtp", JSON_STRING, 0},
	{"audioiface", JSON_STRING, 0},
	{"audioskew", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter rtp_video_parameters[] = {
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"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},
	{"videocodec", JSON_STRING, 0},
	{"videortpmap", JSON_STRING, 0},	/* Deprecated */
	{"videofmtp", JSON_STRING, 0},
	{"videobufferkf", JANUS_JSON_BOOL, 0},	/* Deprecated: see global bufferkf_ms and bufferkf_bytes */
	{"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},
	{"h264sps", JSON_STRING, 0}
};
static struct janus_json_parameter rtp_data_parameters[] = {
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"dataport", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"databuffermsg", JANUS_JSON_BOOL, 0},
	{"datatype", JSON_STRING, 0},
	{"dataiface", JSON_STRING, 0}
};
static struct janus_json_parameter destroy_parameters[] = {
	{"permanent", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter recording_parameters[] = {
	{"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter recording_start_parameters[] = {
	{"media", JANUS_JSON_ARRAY, 0},
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"audio", JSON_STRING, 0},
	{"video", JSON_STRING, 0},
	{"data", JSON_STRING, 0}
};
static struct janus_json_parameter recording_stop_parameters[] = {
	{"media", JANUS_JSON_ARRAY, 0},
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"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},
	{"fallback", 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[] = {
	{"mid", JANUS_JSON_STRING, 0},
	{"streams", JANUS_JSON_ARRAY, 0},
	{"send", JANUS_JSON_BOOL, 0},
	/* For VP8 (or H.264) simulcast */
	{"substream", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"temporal", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"fallback", 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},
	/* For the playout-delay RTP extension, if negotiated */
	{"min_delay", JSON_INTEGER, 0},
	{"max_delay", JSON_INTEGER, 0},
	/* Deprecated parameters: still there only for
	 * backwards compatibility, but not for long */
	{"audio", JSON_STRING, 0},
	{"video", JSON_STRING, 0},
	{"data", JSON_STRING, 0}
};
static struct janus_json_parameter disable_parameters[] = {
	{"stop_recording", JANUS_JSON_BOOL, 0}
};

/* 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 gboolean string_ids = FALSE;
static gboolean ipv6_disabled = FALSE;
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 janus_mutex fd_mutex = JANUS_MUTEX_INITIALIZER;

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;
	uint16_t bufferkf_ms;
	uint32_t bufferkf_bytes;
	/* If enabled, we store the packets of the last keyframe plus the
	 * following deltas (assuming they are within the ms/bytes limits),
	 * so that we can send them as a burst for new viewers */
	GList *latest_keyframe;
	uint32_t kf_ssrc, kf_ts, kf_bytes;
	int64_t kf_start;
	gboolean first_ts;
	janus_mutex mutex;
} janus_streaming_rtp_keyframe;

typedef struct janus_streaming_rtp_relay_packet {
	int mindex;
	janus_rtp_header *data;
	gint length;
	gboolean is_rtp;	/* This may be a data packet and not RTP */
	gboolean is_data;
	gboolean is_video;
	gboolean is_kfburst;
	gboolean simulcast;
	uint32_t ssrc[3];
	janus_videocodec codec;
	int substream;
	int ptype;
	uint32_t timestamp;
	uint16_t seq_number;
	/* The following are only relevant for VP9 SVC*/
	gboolean svc;
	janus_vp9_svc_info svc_info;
	/* The following is only relevant for datachannels */
	gboolean textdata;
} 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_codecs {
	gint pt;
	char *fmtp;
	janus_audiocodec audio_codec;
	janus_videocodec video_codec;
} janus_streaming_codecs;

typedef struct janus_streaming_rtp_source {
	GList *media;				/* List of media streams in this RTP source (audio, video and/or data) */
	GHashTable *media_byid;		/* As above, indexed by mindex */
	GHashTable *media_byfd;		/* As above, indexed by file descriptor */
	janus_mutex rec_mutex;		/* Mutex to protect the recorders of all media streams from race conditions */
	int pipefd[2];				/* Just needed to quickly interrupt the poll when it's time to wrap up */
	int rtp_collision;			/* Whether we should take care of potential RTP collisions */
	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) */
	gboolean rtsp;
#ifdef HAVE_LIBCURL
	CURL *curl;
	char *curl_errbuf;
	janus_streaming_buffer *curldata;
	char *rtsp_url;
	char *rtsp_username, *rtsp_password;
	char *rtsp_stream_uri;
	gboolean rtsp_quirk;
	gboolean rtsp_notify_changes;
	gint64 ka_timeout;
	char *rtsp_ahost, *rtsp_vhost;
	janus_streaming_codecs rtsp_acodecs, rtsp_vcodecs;
	gboolean reconnecting;
	gint64 reconnect_timer;
	gint64 reconnect_delay;
	gint64 session_timeout;
	int rtsp_timeout;
	int rtsp_conn_timeout;
	janus_mutex rtsp_mutex;
#endif
	/* Optional keyframe (and deltas) buffering */
	uint16_t bufferkf_ms;
	uint32_t bufferkf_bytes;
	/* Only needed for SRTP support */
	gboolean is_srtp;
	int srtpsuite;
	char *srtpcrypto;
	srtp_t srtp_ctx;
	srtp_policy_t srtp_policy;
	/* If the media is end-to-end encrypted, we may need to know */
	gboolean e2ee;
	/* Whether the playout-delay extension should be negotiated or not for new subscribers */
	gboolean playoutdelay_ext;
	/* Extension header id in RTP source with abs-capture-time */
	int abscapturetime_src_ext_id;
} janus_streaming_rtp_source;

typedef enum janus_streaming_media {
	JANUS_STREAMING_MEDIA_NONE = 0,
	JANUS_STREAMING_MEDIA_AUDIO,
	JANUS_STREAMING_MEDIA_VIDEO,
	JANUS_STREAMING_MEDIA_DATA
} janus_streaming_media;
static const char *janus_streaming_media_str(janus_streaming_media type) {
	switch(type) {
		case JANUS_STREAMING_MEDIA_AUDIO: return "audio";
		case JANUS_STREAMING_MEDIA_VIDEO: return "video";
		case JANUS_STREAMING_MEDIA_DATA: return "data";
		case JANUS_STREAMING_MEDIA_NONE:
		default:
			break;
	}
	return NULL;
}
static janus_streaming_media janus_streaming_parse_media(const char *type) {
	if(type == NULL)
		return JANUS_STREAMING_MEDIA_NONE;
	else if(!strcasecmp(type, "audio"))
		return JANUS_STREAMING_MEDIA_AUDIO;
	else if(!strcasecmp(type, "video"))
		return JANUS_STREAMING_MEDIA_VIDEO;
	else if(!strcasecmp(type, "data"))
		return JANUS_STREAMING_MEDIA_DATA;
	return JANUS_STREAMING_MEDIA_NONE;
}

typedef struct janus_streaming_rtp_source_stream {
	int mindex;
	janus_streaming_media type;
	char *mid, *label, *msid, *mstid;
	janus_streaming_codecs codecs;
	char *host;
	gint port[3], remote_port;
	gint rtcp_port, remote_rtcp_port;
	in_addr_t mcast;
	char *mcast_str;
	janus_network_address iface;
	char *iface_str;
	janus_recorder *rc;	/* The Janus recorder instance for this streams, if enabled */
	janus_rtp_switching_context context[3];
	int fd[3];
	int rtcp_fd;
	gboolean simulcast;
	gboolean svc;
	char *h264_spspps;
	int h264_spspps_len;
	gboolean skew;
	gint64 last_received[3];
	uint32_t ssrc;				/* Only needed for fixing outgoing RTCP packets */
	uint32_t last_ssrc[3];		/* Only needed for detecting new sources */
	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) */
	struct sockaddr_storage rtcp_addr;
	janus_streaming_rtp_keyframe keyframe;
	gboolean textdata;
	gboolean buffermsg;
	void *last_msg;
	janus_mutex buffermsg_mutex;
	janus_refcount ref;
} janus_streaming_rtp_source_stream;
static void janus_streaming_rtp_source_stream_unref(janus_streaming_rtp_source_stream *stream) {
	/* Decrease the counter */
	if(stream)
		janus_refcount_decrease(&stream->ref);
}

typedef struct janus_streaming_file_source {
	char *filename;
	gboolean opus;
	janus_streaming_codecs codecs;
} 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_mountpoint {
	guint64 id;			/* Unique mountpoint ID (when using integers) */
	gchar *id_str;		/* Unique mountpoint ID (when using strings) */
	char *name;
	char *description;
	char *metadata;
	gboolean is_private;
	char *secret;
	char *pin;
	gboolean enabled;
	gboolean active;
	gboolean audio, video, data;
	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;
	GList *viewers;
	int helper_threads;		/* Only relevant for RTP/RTSP mountpoints */
	GList *threads;			/* Only relevant for RTP/RTSP mountpoints */
	volatile gint destroyed;
	janus_mutex mutex;
	janus_refcount ref;
} janus_streaming_mountpoint;
GHashTable *mountpoints = NULL, *mountpoints_temp = NULL;
janus_mutex mountpoints_mutex = JANUS_MUTEX_INITIALIZER;
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_refcount ref;
} janus_streaming_helper;
static void janus_streaming_helper_destroy(janus_streaming_helper *helper) {
	if(helper && g_atomic_int_compare_and_exchange(&helper->destroyed, 0, 1))
		janus_refcount_decrease(&helper->ref);
}
static void janus_streaming_helper_free(const janus_refcount *helper_ref) {
	janus_streaming_helper *helper = janus_refcount_containerof(helper_ref, janus_streaming_helper, ref);
	/* This helper can be destroyed, free all the resources */
	g_async_queue_unref(helper->queued_packets);
	if(helper->viewers != NULL)
		g_list_free(helper->viewers);
	janus_mutex_destroy(&helper->mutex);
	g_free(helper);
}
static void *janus_streaming_helper_thread(void *data);
static void janus_streaming_helper_rtprtcp_packet(gpointer data, gpointer user_data);

/* Helpers to create an RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
janus_streaming_rtp_source_stream *janus_streaming_create_rtp_source_stream(
		const char *name, int mindex, const char *type, const char *mid, const char *label, const char *msid,
		char *mcast, char *miface, const janus_network_address *iface,
		uint16_t port, uint16_t port2, uint16_t port3, gboolean dortcp, uint16_t rtcpport,
		uint8_t pt, char *codec, char *fmtp, char *sprop,
		gboolean doskew, uint16_t bufferkf_ms, uint32_t bufferkf_bytes, gboolean simulcast, gboolean svc,
		gboolean textdata, gboolean buffermsg);
janus_streaming_mountpoint *janus_streaming_create_rtp_source(
		uint64_t id, char *id_str, char *name, char *desc, char *metadata,
		GList *media, int srtpsuite, char *srtpcrypto, int threads, int rtp_collision,
		uint16_t bufferkf_ms, uint32_t bufferkf_bytes,
		gboolean e2ee, gboolean playoutdelay_ext, int abscapturetime_src_ext_id);
/* Helper to create a file/ondemand live source */
janus_streaming_mountpoint *janus_streaming_create_file_source(
		uint64_t id, char *id_str, char *name, char *desc, char *metadata, char *filename, gboolean live,
		gboolean doaudio, uint8_t apt, char *acodec, char *afmtp, gboolean dovideo);
/* Helper to create a rtsp live source */
janus_streaming_mountpoint *janus_streaming_create_rtsp_source(
		uint64_t id, char *id_str, char *name, char *desc, char *metadata,
		char *url, char *username, char *password,
		gboolean quirk, gboolean notify_changes,
		gboolean doaudio, int audiopt, char *acodec, char *afmtp,
		gboolean dovideo, int videopt, char *vcodec, char *vfmtp,
		uint16_t bufferkf_ms, uint32_t bufferkf_bytes,
		const janus_network_address *iface, int threads,
		gint64 reconnect_delay, gint64 session_timeout, int rtsp_timeout, int rtsp_conn_timeout,
		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_stream {
	int mindex;				/* The media index of this stream (may not be the same as the mountpoint stream) */
	janus_streaming_rtp_source_stream *stream;
	gboolean send;			/* Whether this stream media must be sent to this subscriber */
	int pt;
	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;
	gint64 last_spatial_layer[3];
	int temporal_layer, target_temporal_layer;
	/* Playout delays to enforce when relaying this stream, if the extension has been negotiated */
	int16_t min_delay, max_delay;
} janus_streaming_session_stream;
static void janus_streaming_session_stream_free(janus_streaming_session_stream *s) {
	if(s && s->stream)
		janus_streaming_rtp_source_stream_unref(s->stream);
	g_free(s);
}

typedef struct janus_streaming_session {
	janus_plugin_session *handle;
	janus_streaming_mountpoint *mountpoint;
	gint64 sdp_sessid;
	gint64 sdp_version;
	volatile gint started;
	volatile gint paused;
	GList *streams;				/* List of streams this session is subscribed to */
	GHashTable *streams_byid;	/* Map of streams this session is subscribed to, indexed by mountpoint mindex */
	/* If the media is end-to-end encrypted, we may need to know */
	gboolean e2ee;
	/* Whether the playout-delay extension should be negotiated */
	gboolean playoutdelay_ext;
	/* Extension header id in RTP source with abs-capture-time */
	int abscapturetime_src_ext_id;
	janus_mutex mutex;
	volatile gint dataready;
	volatile gint stopping;
	volatile gint renegotiating;
	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 */
	janus_mutex_destroy(&session->mutex);
	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);
			janus_streaming_helper_destroy(ht);
			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->id_str);
	g_free(mp->name);
	g_free(mp->description);
	g_free(mp->metadata);
	g_free(mp->secret);
	g_free(mp->pin);
	janus_mutex_lock(&mp->mutex);
	if(mp->viewers != NULL)
		g_list_free(mp->viewers);
	if(mp->threads != NULL) {
		/* Remove the last reference to the helper threads, if any */
		GList *l = mp->threads;
		while(l) {
			janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
			janus_refcount_decrease(&ht->ref);
			l = l->next;
		}
		/* Destroy the list */
		g_list_free(mp->threads);
	}
	janus_mutex_unlock(&mp->mutex);
	if(mp->source != NULL && mp->source_destroy != NULL) {
		mp->source_destroy(mp->source);
	}
	janus_mutex_destroy(&mp->mutex);
	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 to notify mountpoint subscribers about events */
static void janus_streaming_notify_subscribers(janus_streaming_mountpoint *mountpoint, json_t *info) {
	/* mountpoint->mutex has to be locked. */
	if(mountpoint == NULL || info == NULL)
		return;
	GList *subscriber = g_list_first(mountpoint->viewers);
	while(subscriber) {
		janus_streaming_session *s = (janus_streaming_session *)subscriber->data;
		if(s == NULL || g_atomic_int_get(&s->destroyed)) {
			subscriber = g_list_next(subscriber);
			continue;
		}
		janus_mutex_lock(&s->mutex);
		gateway->push_event(s->handle, &janus_streaming_plugin, NULL, info, NULL);
		janus_mutex_unlock(&s->mutex);
		subscriber = g_list_next(subscriber);
	}
}

#ifdef HAVE_LIBOGG
/* Helper struct to handle the playout of Opus files */
typedef struct janus_streaming_opus_context {
	char *name, *filename;
	FILE *file;
	ogg_sync_state sync;
	ogg_stream_state stream;
	ogg_page page;
	ogg_packet pkt;
	char *oggbuf;
	gint state, headers;
} janus_streaming_opus_context;
/* Helper method to open an Opus file, and make sure it's valid */
static int janus_streaming_opus_context_init(janus_streaming_opus_context *ctx) {
	if(ctx == NULL || ctx->file == NULL)
		return -1;
	fseek(ctx->file, 0, SEEK_SET);
	ogg_stream_clear(&ctx->stream);
	ogg_sync_clear(&ctx->sync);
	if(ogg_sync_init(&ctx->sync) < 0) {
		JANUS_LOG(LOG_ERR, "[%s] Error re-initializing Ogg sync state...\n", ctx->name);
		return -1;
	}
	ctx->headers = 0;
	return 0;
}
/* Helper method to check if an Ogg page begins with an Ogg stream */
static gboolean janus_streaming_ogg_is_opus(ogg_page *page) {
	ogg_stream_state state;
	ogg_packet pkt;
	ogg_stream_init(&state, ogg_page_serialno(page));
	ogg_stream_pagein(&state, page);
	if(ogg_stream_packetout(&state, &pkt) == 1) {
		if(pkt.bytes >= 19 && !memcmp(pkt.packet, "OpusHead", 8)) {
			ogg_stream_clear(&state);
			return 1;
		}
	}
	ogg_stream_clear(&state);
	return FALSE;
}
/* Helper method to traverse the Opus file until we get a packet we can send */
static int janus_streaming_opus_context_read(janus_streaming_opus_context *ctx, char *buffer, int length) {
	if(ctx == NULL || ctx->file == NULL || buffer == NULL)
		return -1;
	/* Check our current state in processing the Ogg file */
	int read = 0;
	if(ctx->state == 0) {
		/* Prepare a buffer, and read from the Ogg file... */
		ctx->oggbuf = ogg_sync_buffer(&ctx->sync, 8192);
		if(ctx->oggbuf == NULL) {
			JANUS_LOG(LOG_ERR, "[%s] ogg_sync_buffer failed...\n", ctx->name);
			return -2;
		}
		read = fread(ctx->oggbuf, 1, 8192, ctx->file);
		if(read == 0 && feof(ctx->file)) {
			/* FIXME We're doing this forever... should this be configurable? */
			JANUS_LOG(LOG_VERB, "[%s] Rewind! (%s)\n", ctx->name, ctx->filename);
			if(janus_streaming_opus_context_init(ctx) < 0)
				return -3;
			return janus_streaming_opus_context_read(ctx, buffer, length);
		}
		if(ogg_sync_wrote(&ctx->sync, read) < 0) {
			JANUS_LOG(LOG_ERR, "[%s] ogg_sync_wrote failed...\n", ctx->name);
			return -4;
		}
		/* Next state: sync pageout */
		ctx->state = 1;
	}
	if(ctx->state == 1) {
		/* Prepare an ogg_page out of the buffer */
		while((read = ogg_sync_pageout(&ctx->sync, &ctx->page)) == 1) {
			/* Let's look for an Opus stream, first of all */
			if(ctx->headers == 0) {
				if(janus_streaming_ogg_is_opus(&ctx->page)) {
					/* This is the start of an Opus stream */
					if(ogg_stream_init(&ctx->stream, ogg_page_serialno(&ctx->page)) < 0) {
						JANUS_LOG(LOG_ERR, "[%s] ogg_stream_init failed...\n", ctx->name);
						return -5;
					}
					ctx->headers++;
				} else if(!ogg_page_bos(&ctx->page)) {
					/* No Opus stream? */
					JANUS_LOG(LOG_ERR, "[%s] No Opus stream...\n", ctx->name);
					return -6;
				} else {
					/* Still waiting for an Opus stream */
					return janus_streaming_opus_context_read(ctx, buffer, length);
				}
			}
			/* Submit the page for packetization */
			if(ogg_stream_pagein(&ctx->stream, &ctx->page) < 0) {
				JANUS_LOG(LOG_ERR, "[%s] ogg_stream_pagein failed...\n", ctx->name);
				return -7;
			}
			/* Time to start reading packets */
			ctx->state = 2;
			break;
		}
		if(read != 1) {
			/* Go back to reading from the file */
			ctx->state = 0;
			return janus_streaming_opus_context_read(ctx, buffer, length);
		}
	}
	if(ctx->state == 2) {
		/* Read and process available packets */
		if(ogg_stream_packetout(&ctx->stream, &ctx->pkt) != 1) {
			/* Go back to reading pages */
			ctx->state = 1;
			return janus_streaming_opus_context_read(ctx, buffer, length);
		} else {
			/* Skip header packets */
			if(ctx->headers == 1 && ctx->pkt.bytes >= 19 && !memcmp(ctx->pkt.packet, "OpusHead", 8)) {
				ctx->headers++;
				return janus_streaming_opus_context_read(ctx, buffer, length);
			}
			if(ctx->headers == 2 && ctx->pkt.bytes >= 16 && !memcmp(ctx->pkt.packet, "OpusTags", 8)) {
				ctx->headers++;
				return janus_streaming_opus_context_read(ctx, buffer, length);
			}
			/* Get the packet duration */
			if(length < ctx->pkt.bytes) {
				JANUS_LOG(LOG_WARN, "[%s] Buffer too short for Opus packet (%d < %ld)\n",
					ctx->name, length, ctx->pkt.bytes);
				return -8;
			}
			memcpy(buffer, ctx->pkt.packet, ctx->pkt.bytes);
			length = ctx->pkt.bytes;
			return length;
		}
	}
	/* If we got here, continue with the iteration */
	return -9;
}
/* Helper method to cleanup an Opus context */
static void janus_streaming_opus_context_cleanup(janus_streaming_opus_context *ctx) {
	if(ctx == NULL)
		return;
	if(ctx->headers > 0)
		ogg_stream_clear(&ctx->stream);
	ogg_sync_clear(&ctx->sync);
}
#endif


/* Helper method to send an RTCP PLI */
static void janus_streaming_rtcp_pli_send(janus_streaming_rtp_source_stream *stream) {
	if(stream == NULL || stream->rtcp_fd < 0 || stream->rtcp_addr.ss_family == 0)
		return;
	if(!g_atomic_int_compare_and_exchange(&stream->sending_pli, 0, 1))
		return;
	gint64 now = janus_get_monotonic_time();
	if(now - stream->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(&stream->need_pli, 1);
		g_atomic_int_set(&stream->sending_pli, 0);
		return;
	}
	/* Update the time of when we last sent a keyframe request */
	g_atomic_int_set(&stream->need_pli, 0);
	stream->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, stream->ssrc);
	/* Send the packet */
	socklen_t addrlen = stream->rtcp_addr.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
	int sent = 0;
	if((sent = sendto(stream->rtcp_fd, rtcp_buf, rtcp_len, 0,
			(struct sockaddr *)&stream->rtcp_addr, addrlen)) < 0) {
		JANUS_LOG(LOG_ERR, "Error in sendto... %d (%s)\n", errno, g_strerror(errno));
	} else {
		JANUS_LOG(LOG_HUGE, "Sent %d/%d bytes\n", sent, rtcp_len);
	}
	g_atomic_int_set(&stream->sending_pli, 0);
}

/* Helper method to send an RTCP REMB */
static void janus_streaming_rtcp_remb_send(janus_streaming_rtp_source *source, janus_streaming_rtp_source_stream *stream) {
	if(stream == NULL || stream->rtcp_fd < 0 || stream->rtcp_addr.ss_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, stream->ssrc);
	JANUS_LOG(LOG_HUGE, "Sending REMB: %"SCNu32"\n", source->lowest_bitrate);
	/* Reset the lowest bitrate */
	source->lowest_bitrate = 0;
	/* Send the packet */
	socklen_t addrlen = stream->rtcp_addr.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
	int sent = 0;
	if((sent = sendto(stream->rtcp_fd, rtcp_buf, rtcp_len, 0,
			(struct sockaddr *)&stream->rtcp_addr, addrlen)) < 0) {
		JANUS_LOG(LOG_ERR, "Error in sendto... %d (%s)\n", errno, g_strerror(errno));
	} else {
		JANUS_LOG(LOG_HUGE, "Sent %d/%d bytes\n", sent, rtcp_len);
	}
}

/* Helper method to parse a base64-encoded sprop-parameter-sets and update a stream */
static char *janus_streaming_parse_sprop(char *sprop, int *len) {
	if(sprop == NULL || len == NULL)
		return NULL;
	char *sps = NULL;
	if(strstr(sprop, ",")) {
		char **parts = g_strsplit(sprop, ",", -1);
		char *sps_p = parts[0];
		char *pps_p = parts[1];
		if(sps_p && pps_p) {
			/* Base64 decode both fields */
			gsize slen = 0, plen = 0;
			guchar *sps_dec = g_base64_decode(sps_p, &slen);
			guchar *pps_dec = g_base64_decode(pps_p, &plen);
			if(sps_dec && pps_dec) {
				/* Prepare the RTP packet with the NAL units */
				int flen = 12 + 1 + 2 + slen + 2 + plen;
				char *buf = g_malloc0(flen);
				janus_rtp_header *rtp = (janus_rtp_header *)buf;
				rtp->version = 2;
				/* STAP-A */
				char *nal = buf + 12;
				*nal = 0x18;
				int offset = 1;
				/* Add SPS */
				uint16_t nsize = htons(slen);
				memcpy(nal + offset, &nsize, sizeof(nsize));
				offset += sizeof(nsize);
				memcpy(nal + offset, sps_dec, slen);
				offset += slen;
				/* Add PPS */
				nsize = htons(plen);
				memcpy(nal + offset, &nsize, sizeof(nsize));
				offset += sizeof(nsize);
				memcpy(nal + offset, pps_dec, plen);
				/* Keep track of the packet */
				sps = buf;
				*len = flen;
			}
			g_free(sps_dec);
			g_free(pps_dec);
		}
		g_strfreev(parts);
	}
	return sps;
}

/* Helper method to check if a file has a specific extension */
static gboolean janus_streaming_check_extension(const char *filename, const char *extension) {
	if(filename == NULL || extension == NULL)
		return FALSE;
	size_t flen = strlen(filename);
	size_t elen = strlen(extension);
	if(flen == 0 || elen == 0 || flen < elen)
		return FALSE;
	const char *suffix = filename + flen - elen;
	return (strstr(suffix, extension) == suffix);
}

/* 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_INVALID_STATE			460
#define JANUS_STREAMING_ERROR_INVALID_SDP			461
#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);
#else
	JANUS_LOG(LOG_WARN, "libcurl not available, Streaming plugin will not have RTSP support\n");
#endif
#ifndef HAVE_LIBOGG
	JANUS_LOG(LOG_WARN, "libogg not available, Streaming plugin will not have file-based Opus streaming\n");
#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... %d (%s)\n",
			errno, g_strerror(errno));
	}

	/* 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);

	/* Let's check if IPv6 is disabled, as we may need when creating sockets */
	int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
	if(fd < 0) {
		ipv6_disabled = TRUE;
	} else {
		int v6only = 0;
		if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0)
			ipv6_disabled = TRUE;
	}
	if(fd >= 0)
		close(fd);
	if(ipv6_disabled) {
		JANUS_LOG(LOG_WARN, "IPv6 disabled, will only use IPv4 sockets for mountpoints\n");
	}

	/* 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++;
				if(janus_string_to_uint16(range->value, &rtp_range_min) < 0)
					JANUS_LOG(LOG_WARN, "Invalid RTP min port value: %s (assuming 0)\n", range->value);
				if(janus_string_to_uint16(maxport, &rtp_range_max) < 0)
					JANUS_LOG(LOG_WARN, "Invalid RTP max port value: %s (assuming 0)\n", 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);
		}
		janus_config_item *ids = janus_config_get(config, config_general, janus_config_type_item, "string_ids");
		if(ids != NULL && ids->value != NULL)
			string_ids = janus_is_true(ids->value);
		if(string_ids) {
			JANUS_LOG(LOG_INFO, "Streaming will use alphanumeric IDs, not numeric\n");
		}
	}
	/* Iterate on all mountpoints */
	mountpoints = g_hash_table_new_full(string_ids ? g_str_hash : g_int64_hash, string_ids ? g_str_equal : g_int64_equal,
		(GDestroyNotify)g_free, (GDestroyNotify)janus_streaming_mountpoint_destroy);
	mountpoints_temp = g_hash_table_new_full(string_ids ? g_str_hash : g_int64_hash, string_ids ? g_str_equal : g_int64_equal,
		(GDestroyNotify)g_free, NULL);
	if(config != NULL) {
		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 Streaming mountpoint '%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 mountpoint '%s'...\n", cat->name);
				cl = cl->next;
				continue;
			}
			janus_config_item *id = janus_config_get(config, cat, janus_config_type_item, "id");
			guint64 mpid = 0;
			if(id == NULL || id->value == NULL) {
				JANUS_LOG(LOG_VERB, "Missing id for mountpoint '%s', will generate a random one...\n", cat->name);
			} else {
				janus_mutex_lock(&mountpoints_mutex);
				if(!string_ids) {
					mpid = g_ascii_strtoull(id->value, 0, 10);
					/* Make sure the ID is completely numeric */
					char mpid_str[30];
					g_snprintf(mpid_str, sizeof(mpid_str), "%"SCNu64, mpid);
					if(strcmp(id->value, mpid_str)) {
						janus_mutex_unlock(&mountpoints_mutex);
						JANUS_LOG(LOG_ERR, "Can't add the Streaming mountpoint '%s', ID '%s' is not numeric...\n",
							cat->name, id->value);
						cl = cl->next;
						continue;
					}
					if(mpid == 0) {
						janus_mutex_unlock(&mountpoints_mutex);
						JANUS_LOG(LOG_ERR, "Can't add the Streaming mountpoint '%s', invalid ID '%s'...\n",
							cat->name, id->value);
						cl = cl->next;
						continue;
					}
				}
				/* Let's make sure the mountpoint doesn't exist already */
				if(g_hash_table_lookup(mountpoints, string_ids ? (gpointer)id->value : (gpointer)&mpid) != NULL) {
					/* It does... */
					janus_mutex_unlock(&mountpoints_mutex);
					JANUS_LOG(LOG_ERR, "Can't add the Streaming mountpoint '%s', ID '%s' already exists...\n",
						cat->name, id->value);
					cl = cl->next;
					continue;
				}
				janus_mutex_unlock(&mountpoints_mutex);
			}
			if(!strcasecmp(type->value, "rtp")) {
				/* RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
				GList *streams = NULL;
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *md = janus_config_get(config, cat, janus_config_type_item, "metadata");
				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 *media = janus_config_get(config, cat, janus_config_type_array, "media");
				janus_config_item *rtpcollision = janus_config_get(config, cat, janus_config_type_item, "collision");
				janus_config_item *vkf_ms = janus_config_get(config, cat, janus_config_type_item, "bufferkf_ms");
				janus_config_item *vkf_bytes = janus_config_get(config, cat, janus_config_type_item, "bufferkf_bytes");
				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");
				janus_config_item *e2ee = janus_config_get(config, cat, janus_config_type_item, "e2ee");
				janus_config_item *pd = janus_config_get(config, cat, janus_config_type_item, "playoutdelay_ext");
				janus_config_item *abscaptime_src_id = janus_config_get(config, cat, janus_config_type_item, "abscapturetime_src_ext_id");
				gboolean is_private = priv && priv->value && janus_is_true(priv->value);
				if(ssuite && ssuite->value && atoi(ssuite->value) != 32 && atoi(ssuite->value) != 80) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%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' mountpoint '%s', invalid collision configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				uint16_t bufferkf_ms = 0;
				if(vkf_ms && vkf_ms->value && janus_string_to_uint16(vkf_ms->value, &bufferkf_ms) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid bufferkf_ms configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				uint32_t bufferkf_bytes = 0;
				if(vkf_bytes && vkf_bytes->value && janus_string_to_uint32(vkf_bytes->value, &bufferkf_bytes) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid bufferkf_bytes configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(threads && threads->value && atoi(threads->value) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid threads configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				int abscaptime_src_id_int = 0;
				if(abscaptime_src_id && abscaptime_src_id->value) {
					abscaptime_src_id_int = atoi(abscaptime_src_id->value);
					if(abscaptime_src_id_int < 0 || abscaptime_src_id_int > 14) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid abscaptime_src_id configuration...\n", cat->name);
						cl = cl->next;
						continue;
					}
				}
				/* How are we adding media? */
				if(media != NULL) {
					/* We're using the new media-based configuration, iterate on all media objects */
					gboolean failed = FALSE;
					uint16_t s_bufferkf_ms = 0;
					uint32_t s_bufferkf_bytes = 0;
					GList *ml = media->list;
					while(ml) {
						janus_config_item *m = (janus_config_item *)ml->data;
						if(m == NULL || m->type != janus_config_type_category) {
							JANUS_LOG(LOG_WARN, "  -- Invalid media item (not a category?), skipping in '%s'...\n", cat->name);
							ml = ml->next;
							continue;
						}
						janus_config_item *type = janus_config_get(config, m, janus_config_type_item, "type");
						if(type == NULL || type->value == NULL) {
							JANUS_LOG(LOG_WARN, "  -- Invalid media type, skipping in '%s'...\n", cat->name);
							ml = ml->next;
							continue;
						}
						if(strcasecmp(type->value, "audio") && strcasecmp(type->value, "video") && strcasecmp(type->value, "data")) {
							JANUS_LOG(LOG_WARN, "  -- Unsupported media type '%s', skipping in '%s'...\n", type->value, cat->name);
							ml = ml->next;
							continue;
						}
						gboolean audio = !strcasecmp(type->value, "audio");
						gboolean video = !strcasecmp(type->value, "video");
						gboolean data = !strcasecmp(type->value, "data");
						/* We need mid and label for addressing this stream on the client side */
						janus_config_item *mid = janus_config_get(config, m, janus_config_type_item, "mid");
						if(mid == NULL || mid->value == NULL) {
							JANUS_LOG(LOG_WARN, "  -- Missing media mid, skipping in '%s'...\n", cat->name);
							ml = ml->next;
							continue;
						}
						janus_config_item *label = janus_config_get(config, m, janus_config_type_item, "label");
						janus_config_item *msid = janus_config_get(config, m, janus_config_type_item, "msid");
						/* These are the attributes we can configure per each media stream */
						janus_network_address media_iface;
						janus_config_item *iface = janus_config_get(config, m, janus_config_type_item, "iface");
						janus_config_item *mcast = janus_config_get(config, m, janus_config_type_item, "mcast");
						janus_config_item *port = janus_config_get(config, m, janus_config_type_item, "port");
						janus_config_item *rtcpport = janus_config_get(config, m, janus_config_type_item, "rtcpport");
						janus_config_item *pt = janus_config_get(config, m, janus_config_type_item, "pt");
						janus_config_item *codec = janus_config_get(config, m, janus_config_type_item, "codec");
						janus_config_item *rtpmap = janus_config_get(config, m, janus_config_type_item, "rtpmap");
						janus_config_item *fmtp = janus_config_get(config, m, janus_config_type_item, "fmtp");
						janus_config_item *vsps = janus_config_get(config, m, janus_config_type_item, "h264sps");
						janus_config_item *vkf = janus_config_get(config, m, janus_config_type_item, "bufferkf");
						janus_config_item *vsc = janus_config_get(config, m, janus_config_type_item, "simulcast");
						janus_config_item *dbm = janus_config_get(config, m, janus_config_type_item, "buffermsg");
						janus_config_item *dt = janus_config_get(config, m, janus_config_type_item, "datatype");
						janus_config_item *vport2 = janus_config_get(config, m, janus_config_type_item, "port2");
						janus_config_item *vport3 = janus_config_get(config, m, janus_config_type_item, "port3");
						janus_config_item *vsvc = janus_config_get(config, m, janus_config_type_item, "svc");
						janus_config_item *skew = janus_config_get(config, m, janus_config_type_item, "skew");
						gboolean doskew = skew && skew->value && janus_is_true(skew->value);
						gboolean dosvc = video && vsvc && vsvc->value && janus_is_true(vsvc->value);
						if(video && vkf && vkf->value && janus_is_true(vkf->value)) {
							JANUS_LOG(LOG_WARN, "The bufferkf property has been deprecated, please refer to bufferkf_ms and/or bufferkf_bytes\n");
						}
						s_bufferkf_ms = video ? bufferkf_ms : 0;
						s_bufferkf_bytes = video ? bufferkf_bytes : 0;
						gboolean simulcast = video && vsc && vsc->value && janus_is_true(vsc->value);
						if(simulcast && (s_bufferkf_ms || s_bufferkf_bytes)) {
							/* FIXME We'll need to take care of this */
							JANUS_LOG(LOG_WARN, "Simulcasting enabled, so disabling buffering of keyframes for this stream\n");
							s_bufferkf_ms = 0;
							s_bufferkf_bytes = 0;
						}
						gboolean buffermsg = data && dbm && dbm->value && janus_is_true(dbm->value);
						gboolean textdata = TRUE;
						if(data && dt && dt->value) {
							if(!strcasecmp(dt->value, "text"))
								textdata = TRUE;
							else if(!strcasecmp(dt->value, "binary"))
								textdata = FALSE;
							else {
								JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid data type '%s'...\n", cat->name, dt->value);
								failed = TRUE;
								break;
							}
						}
						const char *streamcodec = (codec && codec->value ? codec->value : NULL);
						if((audio || video) && streamcodec == NULL) {
							/* No codec property, check the deprecated rtpmap */
							if(rtpmap && rtpmap->value)
								streamcodec = janus_sdp_get_rtpmap_codec(rtpmap->value);
						}
						if((audio || video) &&
								(port == NULL || port->value == NULL || atoi(port->value) < 0 ||
								pt == NULL || pt->value == NULL || streamcodec == NULL)) {
							JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', missing mandatory information for audio/video stream...\n", cat->name);
							failed = TRUE;
							break;
						} else if(data && (port == NULL || port->value == NULL || atoi(port->value) < 0)) {
							JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', missing mandatory information for data stream...\n", cat->name);
							failed = TRUE;
							break;
						}
						if(iface) {
							if(!ifas) {
								JANUS_LOG(LOG_ERR, "Can't add '%s' stream '%s', it relies on network configuration but network device information is unavailable...\n", type->value, cat->name);
								failed = TRUE;
								break;
							}
							if(janus_network_lookup_interface(ifas, iface->value, &media_iface) != 0) {
								JANUS_LOG(LOG_ERR, "Can't add '%s' stream '%s', invalid network interface configuration for media stream...\n", type->value, cat->name);
								failed = TRUE;
								break;
							}
						}
						/* Create the source stream */
						janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
							cat->name, g_list_length(streams),
							type->value, mid->value, (label && label->value ? label->value : type->value),
							(msid && msid->value ? msid->value : NULL),
							mcast ? (char *)mcast->value : NULL,
							iface && iface->value ? (char *)iface->value : NULL,
							iface && iface->value ? &media_iface : NULL,
							(port && port->value) ? atoi(port->value) : 0,
							(vport2 && vport2->value) ? atoi(vport2->value) : 0,
							(vport3 && vport3->value) ? atoi(vport3->value) : 0,
							(rtcpport && rtcpport->value),
								(rtcpport && rtcpport->value) ? atoi(rtcpport->value) : 0,
							(pt && pt->value) ? atoi(pt->value) : 0,
							(char *)streamcodec,
							fmtp ? (char *)fmtp->value : NULL,
							vsps ? (char *)vsps->value : NULL,
							doskew, s_bufferkf_ms, s_bufferkf_bytes,
							simulcast, dosvc, textdata, buffermsg);
						if(stream == NULL) {
							JANUS_LOG(LOG_ERR, "Can't add '%s' stream '%s', error creating source stream...\n", type->value, cat->name);
							failed = TRUE;
							break;
						}
						/* Add to the list of streams */
						streams = g_list_append(streams, stream);
						/* Go on */
						ml = ml->next;
					}
					if(failed) {
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						cl = cl->next;
						continue;
					}
				} else {
					/* If we got here, we create a mountpoint the "old" way */
					janus_network_address video_iface, audio_iface, data_iface;
					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 *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 *apt = janus_config_get(config, cat, janus_config_type_item, "audiopt");
					janus_config_item *acodec = janus_config_get(config, cat, janus_config_type_item, "audiocodec");
					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 *vpt = janus_config_get(config, cat, janus_config_type_item, "videopt");
					janus_config_item *vcodec = janus_config_get(config, cat, janus_config_type_item, "videocodec");
					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 *vsps = janus_config_get(config, cat, janus_config_type_item, "h264sps");
					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 *dmcast = janus_config_get(config, cat, janus_config_type_item, "datamcast");
					janus_config_item *diface = janus_config_get(config, cat, janus_config_type_item, "dataiface");
					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 *dt = janus_config_get(config, cat, janus_config_type_item, "datatype");
					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);
					if(video && vkf && vkf->value && janus_is_true(vkf->value)) {
						JANUS_LOG(LOG_WARN, "The videobufferkf property has been deprecated, please refer to bufferkf_ms and/or bufferkf_bytes\n");
					}
					gboolean simulcast = video && vsc && vsc->value && janus_is_true(vsc->value);
					if(simulcast && (bufferkf_ms > 0 || bufferkf_bytes > 0)) {
						/* FIXME We'll need to take care of this */
						JANUS_LOG(LOG_WARN, "Simulcasting enabled, so disabling buffering of keyframes for this stream\n");
						bufferkf_ms = 0;
						bufferkf_bytes = 0;
					}
					gboolean buffermsg = data && dbm && dbm->value && janus_is_true(dbm->value);
					gboolean textdata = TRUE;
					if(data && dt && dt->value) {
						if(!strcasecmp(dt->value, "text"))
							textdata = TRUE;
						else if(!strcasecmp(dt->value, "binary"))
							textdata = FALSE;
						else {
							JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid data type '%s'...\n", cat->name, dt->value);
							cl = cl->next;
							continue;
						}
					}
					if(!doaudio && !dovideo && !dodata) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', no audio, video or data have to be streamed...\n", cat->name);
						cl = cl->next;
						continue;
					}
					uint16_t audio_port = 0, audio_rtcp_port = 0;
					const char *audiocodec = (acodec && acodec->value ? acodec->value : NULL);
					if(audiocodec == NULL) {
						/* No audiocodec property, check the deprecated audiortpmap */
						if(artpmap && artpmap->value)
							audiocodec = janus_sdp_get_rtpmap_codec(artpmap->value);
					}
					if(doaudio &&
							(aport == NULL || aport->value == NULL ||
							janus_string_to_uint16(aport->value, &audio_port) < 0 ||
							apt == NULL || apt->value == NULL || audiocodec == NULL)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', missing mandatory information for audio...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(doaudio && artcpport != NULL && artcpport->value != NULL &&
							(janus_string_to_uint16(artcpport->value, &audio_rtcp_port) < 0)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid audio RTCP port...\n", cat->name);
						cl = cl->next;
						continue;
					}
					gboolean doaudiortcp = (artcpport != NULL && artcpport->value != NULL);
					if(doaudio && aiface) {
						if(!ifas) {
							JANUS_LOG(LOG_ERR, "Skipping 'rtp' mountpoint '%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' mountpoint '%s', invalid network interface configuration for audio...\n", cat->name);
							cl = cl->next;
							continue;
						}
					}
					uint16_t video_port = 0, video_port2 = 0, video_port3 = 0, video_rtcp_port = 0;
					const char *videocodec = (vcodec && vcodec->value ? vcodec->value : NULL);
					if(videocodec == NULL) {
						/* No videocodec property, check the deprecated videortpmap */
						if(vrtpmap && vrtpmap->value)
							videocodec = janus_sdp_get_rtpmap_codec(vrtpmap->value);
					}
					if(dovideo &&
							(vport == NULL || vport->value == NULL ||
							janus_string_to_uint16(vport->value, &video_port) < 0 ||
							vpt == NULL || vpt->value == NULL || videocodec == NULL)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', missing mandatory information for video...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(dovideo && vrtcpport != NULL && vrtcpport->value != NULL &&
							(janus_string_to_uint16(vrtcpport->value, &video_rtcp_port) < 0)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid video RTCP port...\n", cat->name);
						cl = cl->next;
						continue;
					}
					gboolean dovideortcp = (vrtcpport != NULL && vrtcpport->value != NULL);
					if(dovideo && vport2 != NULL && vport2->value != NULL &&
							(janus_string_to_uint16(vport2->value, &video_port2) < 0)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid simulcast port...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(dovideo && vport3 != NULL && vport3->value != NULL &&
							(janus_string_to_uint16(vport3->value, &video_port3) < 0)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid simulcast port...\n", cat->name);
						cl = cl->next;
						continue;
					}
					if(dovideo && viface) {
						if(!ifas) {
							JANUS_LOG(LOG_ERR, "Skipping 'rtp' mountpoint '%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' mountpoint '%s', invalid network interface configuration for video...\n", cat->name);
							cl = cl->next;
							continue;
						}
					}
					uint16_t data_port = 0;
					if(dodata && (dport == NULL || dport->value == NULL ||
							janus_string_to_uint16(dport->value, &data_port) < 0)) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%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' mountpoint '%s': no datachannels support......\n", cat->name);
						cl = cl->next;
						continue;
					}
	#endif
					if(dodata && diface) {
						if(!ifas) {
							JANUS_LOG(LOG_ERR, "Skipping 'rtp' mountpoint '%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' mountpoint '%s', invalid network interface configuration for data...\n", cat->name);
							cl = cl->next;
							continue;
						}
					}
					/* Create the individual streams */
					if(doaudio) {
						/* Create the audio source stream */
						janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
							cat->name, g_list_length(streams),
							"audio", "a", "audio", NULL,
							amcast ? (char *)amcast->value : NULL,
							aiface && aiface->value ? (char *)aiface->value : NULL,
							aiface && aiface->value ? &audio_iface : NULL,
							(aport && aport->value) ? atoi(aport->value) : 0, 0, 0,
							doaudiortcp, (artcpport && artcpport->value) ? atoi(artcpport->value) : 0,
							(apt && apt->value) ? atoi(apt->value) : 0,
							(char *)audiocodec,
							afmtp ? (char *)afmtp->value : NULL, NULL,
							doaskew, 0, 0, FALSE, FALSE, FALSE, FALSE);
						if(stream == NULL) {
							JANUS_LOG(LOG_ERR, "Skipping 'audio' stream '%s', error creating source stream...\n", cat->name);
							g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
							cl = cl->next;
							continue;
						}
						/* Add to the list of streams */
						streams = g_list_append(streams, stream);
					}
					if(dovideo) {
						/* Create the video source stream */
						janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
							cat->name, g_list_length(streams),
							"video", "v", "video", NULL,
							vmcast ? (char *)vmcast->value : NULL,
							viface && viface->value ? (char *)viface->value : NULL,
							viface && viface->value ? &video_iface : NULL,
							(vport && vport->value) ? atoi(vport->value) : 0,
							(vport2 && vport2->value) ? atoi(vport2->value) : 0,
							(vport3 && vport3->value) ? atoi(vport3->value) : 0,
							dovideortcp, (vrtcpport && vrtcpport->value) ? atoi(vrtcpport->value) : 0,
							(vpt && vpt->value) ? atoi(vpt->value) : 0,
							(char *)videocodec,
							vfmtp ? (char *)vfmtp->value : NULL,
							vsps ? (char *)vsps->value : NULL,
							dovskew, bufferkf_ms, bufferkf_bytes,
							simulcast, dosvc, FALSE, FALSE);
						if(stream == NULL) {
							JANUS_LOG(LOG_ERR, "Skipping 'video' stream '%s', error creating source stream...\n", cat->name);
							g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
							cl = cl->next;
							continue;
						}
						/* Add to the list of streams */
						streams = g_list_append(streams, stream);
					}
					if(dodata) {
						/* Create the data source stream */
						janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
							cat->name, g_list_length(streams),
							"data", "d", "data", NULL,
							dmcast ? (char *)dmcast->value : NULL,
							diface && diface->value ? (char *)diface->value : NULL,
							diface && diface->value ? &data_iface : NULL,
							(dport && dport->value) ? atoi(dport->value) : 0,
							0, 0, FALSE, 0,
							0, NULL, NULL, NULL,
							FALSE, 0, 0, FALSE, FALSE, textdata, buffermsg);
						if(stream == NULL) {
							JANUS_LOG(LOG_ERR, "Skipping 'data' stream '%s', error creating source stream...\n", cat->name);
							g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
							cl = cl->next;
							continue;
						}
						/* Add to the list of streams */
						streams = g_list_append(streams, stream);
					}
				}
				/* Streams created, create the actual mountpoint now */
				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_rtp_source(
						mpid, (char *)(id ? id->value : NULL),
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						md ? (char *)md->value : NULL,
						streams,
						ssuite && ssuite->value ? atoi(ssuite->value) : 0,
						scrypto && scrypto->value ? (char *)scrypto->value : NULL,
						(threads && threads->value) ? atoi(threads->value) : 0,
						(rtpcollision && rtpcollision->value) ?  atoi(rtpcollision->value) : 0,
						bufferkf_ms, bufferkf_bytes,
						(e2ee && e2ee->value) ? janus_is_true(e2ee->value) : FALSE,
						(pd && pd->value) ? janus_is_true(pd->value) : FALSE,
						abscaptime_src_id_int)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'rtp' mountpoint '%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-based live source */
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *md = janus_config_get(config, cat, janus_config_type_item, "metadata");
				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 *apt = janus_config_get(config, cat, janus_config_type_item, "audiopt");
				janus_config_item *acodec = janus_config_get(config, cat, janus_config_type_item, "audiocodec");
				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 *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' mountpoint '%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);
				/* We only support audio for file-based streaming at the moment: for streaming
				 * files using other codecs/formats an external tools should feed us RTP instead */
				if(!doaudio || dovideo) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' mountpoint '%s', we only support audio file streaming right now...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(strstr(file->value, "../") != NULL) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' mountpoint '%s', can't use relative paths\n", cat->name);
					cl = cl->next;
					continue;
				}
#ifdef HAVE_LIBOGG
				if(!janus_streaming_check_extension(file->value, ".opus") &&
						!janus_streaming_check_extension(file->value, ".alaw") &&
						!janus_streaming_check_extension(file->value, ".mulaw")) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' mountpoint '%s', unsupported format (we only support Opus and raw mu-Law/a-Law files right now)\n", cat->name);
#else
				if(!janus_streaming_check_extension(file->value, ".alaw") &&
						!janus_streaming_check_extension(file->value, ".mulaw")) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' mountpoint '%s', unsupported format (we only support raw mu-Law and a-Law files right now)\n", cat->name);
#endif
					cl = cl->next;
					continue;
				}
				FILE *audiofile = fopen(file->value, "rb");
				if(!audiofile) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' mountpoint, no such file '%s'...\n", file->value);
					cl = cl->next;
					continue;
				}
				fclose(audiofile);
				const char *audiocodec = (acodec && acodec->value ? acodec->value : NULL);
				if(audiocodec == NULL) {
					/* No audiocodec property, check the deprecated audiortpmap */
					if(artpmap && artpmap->value)
						audiocodec = janus_sdp_get_rtpmap_codec(artpmap->value);
				}

				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_file_source(
						mpid, (char *)(id ? id->value : NULL),
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						md ? (char *)md->value : NULL,
						(char *)file->value, TRUE,
						doaudio,
						(apt && apt->value) ? atoi(apt->value) : 0,
						(char *)audiocodec,
						afmtp ? (char *)afmtp->value : NULL,
						dovideo)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'live' mountpoint '%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")) {
				/* File-based on demand source */
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *md = janus_config_get(config, cat, janus_config_type_item, "metadata");
				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 *apt = janus_config_get(config, cat, janus_config_type_item, "audiopt");
				janus_config_item *acodec = janus_config_get(config, cat, janus_config_type_item, "audiocodec");
				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 *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' mountpoint '%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);
				/* We only support audio for file-based streaming at the moment: for streaming
				 * files using other codecs/formats an external tools should feed us RTP instead */
				if(!doaudio || dovideo) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' mountpoint '%s', we only support audio file streaming right now...\n", cat->name);
					cl = cl->next;
					continue;
				}
				if(strstr(file->value, "../") != NULL) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' mountpoint '%s', can't use relative paths\n", cat->name);
					cl = cl->next;
					continue;
				}
#ifdef HAVE_LIBOGG
				if(!janus_streaming_check_extension(file->value, ".opus") &&
						!janus_streaming_check_extension(file->value, ".alaw") &&
						!janus_streaming_check_extension(file->value, ".mulaw")) {
					JANUS_LOG(LOG_ERR, "Can't add 'live' mountpoint '%s', unsupported format (we only support Opus and raw mu-Law/a-Law files right now)\n", cat->name);
#else
				if(!janus_streaming_check_extension(file->value, ".alaw") &&
						!janus_streaming_check_extension(file->value, ".mulaw")) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' mountpoint '%s', unsupported format (we only support raw mu-Law and a-Law files right now)\n", cat->name);
#endif
					cl = cl->next;
					continue;
				}
				FILE *audiofile = fopen(file->value, "rb");
				if(!audiofile) {
					JANUS_LOG(LOG_ERR, "Can't add 'ondemand' mountpoint, no such file '%s'...\n", file->value);
					cl = cl->next;
					continue;
				}
				fclose(audiofile);
				const char *audiocodec = (acodec && acodec->value ? acodec->value : NULL);
				if(audiocodec == NULL) {
					/* No audiocodec property, check the deprecated audiortpmap */
					if(artpmap && artpmap->value)
						audiocodec = janus_sdp_get_rtpmap_codec(artpmap->value);
				}

				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_file_source(
						mpid, (char *)(id ? id->value : NULL),
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						md ? (char *)md->value : NULL,
						(char *)file->value, FALSE,
						doaudio,
						(apt && apt->value) ? atoi(apt->value) : 0,
						(char *)audiocodec,
						afmtp ? (char *)afmtp->value : NULL,
						dovideo)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'ondemand' mountpoint '%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' mountpoint '%s', libcurl support not compiled...\n", cat->name);
				cl = cl->next;
				continue;
#else
				janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
				janus_config_item *md = janus_config_get(config, cat, janus_config_type_item, "metadata");
				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 *quirk = janus_config_get(config, cat, janus_config_type_item, "rtsp_quirk");
				janus_config_item *audio = janus_config_get(config, cat, janus_config_type_item, "audio");
				janus_config_item *acodec = janus_config_get(config, cat, janus_config_type_item, "audiocodec");
				janus_config_item *artpmap = janus_config_get(config, cat, janus_config_type_item, "audiortpmap");
				janus_config_item *apt = 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 *vpt = janus_config_get(config, cat, janus_config_type_item, "videopt");
				janus_config_item *vcodec = janus_config_get(config, cat, janus_config_type_item, "videocodec");
				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 *vkf_ms = janus_config_get(config, cat, janus_config_type_item, "bufferkf_ms");
				janus_config_item *vkf_bytes = janus_config_get(config, cat, janus_config_type_item, "bufferkf_bytes");
				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_config_item *threads = janus_config_get(config, cat, janus_config_type_item, "threads");
				janus_config_item *reconnect_delay = janus_config_get(config, cat, janus_config_type_item, "rtsp_reconnect_delay");
				janus_config_item *session_timeout = janus_config_get(config, cat, janus_config_type_item, "rtsp_session_timeout");
				janus_config_item *rtsp_timeout = janus_config_get(config, cat, janus_config_type_item, "rtsp_timeout");
				janus_config_item *rtsp_conn_timeout = janus_config_get(config, cat, janus_config_type_item, "rtsp_conn_timeout");
				janus_config_item *rtsp_notify_changes = janus_config_get(config, cat, janus_config_type_item, "rtsp_notify_changes");
				janus_network_address iface_value;
				if(file == NULL || file->value == NULL) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtsp' mountpoint '%s', missing mandatory information...\n", cat->name);
					cl = cl->next;
					continue;
				}
				gboolean is_private = priv && priv->value && janus_is_true(priv->value);
				gboolean rtsp_quirk = quirk && quirk->value && janus_is_true(quirk->value);
				gboolean notify_changes = rtsp_notify_changes && rtsp_notify_changes->value && janus_is_true(rtsp_notify_changes->value);
				gboolean doaudio = audio && audio->value && janus_is_true(audio->value);
				gboolean dovideo = video && video->value && janus_is_true(video->value);
				if(video && vkf && vkf->value && janus_is_true(vkf->value)) {
					JANUS_LOG(LOG_WARN, "The videobufferkf property has been deprecated, please refer to bufferkf_ms and/or bufferkf_bytes\n");
				}
				uint16_t bufferkf_ms = 0;
				if(vkf_ms && vkf_ms->value && janus_string_to_uint16(vkf_ms->value, &bufferkf_ms) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtsp' mountpoint '%s', invalid bufferkf_ms configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				uint32_t bufferkf_bytes = 0;
				if(vkf_bytes && vkf_bytes->value && janus_string_to_uint32(vkf_bytes->value, &bufferkf_bytes) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtsp' mountpoint '%s', invalid bufferkf_bytes configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}
				gboolean error_on_failure = TRUE;
				if(failerr && failerr->value)
					error_on_failure = janus_is_true(failerr->value);
				if(threads && threads->value && atoi(threads->value) < 0) {
					JANUS_LOG(LOG_ERR, "Can't add 'rtsp' mountpoint '%s', invalid threads configuration...\n", cat->name);
					cl = cl->next;
					continue;
				}

				if((doaudio || dovideo) && iface && iface->value) {
					if(!ifas) {
						JANUS_LOG(LOG_ERR, "Skipping 'rtsp' mountpoint '%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' mountpoint '%s', invalid network interface configuration for stream...\n", cat->name);
						cl = cl->next;
						continue;
					}
				}

				const char *audiocodec = (acodec && acodec->value ? acodec->value : NULL);
				if(audiocodec == NULL) {
					/* No audiocodec property, check the deprecated audiortpmap */
					if(artpmap && artpmap->value)
						audiocodec = janus_sdp_get_rtpmap_codec(artpmap->value);
				}
				const char *videocodec = (vcodec && vcodec->value ? vcodec->value : NULL);
				if(videocodec == NULL) {
					/* No videocodec property, check the deprecated videortpmap */
					if(vrtpmap && vrtpmap->value)
						videocodec = janus_sdp_get_rtpmap_codec(vrtpmap->value);
				}

				janus_streaming_mountpoint *mp = NULL;
				if((mp = janus_streaming_create_rtsp_source(
						mpid, (char *)(id ? id->value : NULL),
						(char *)cat->name,
						desc ? (char *)desc->value : NULL,
						md ? (char *)md->value : NULL,
						(char *)file->value,
						username ? (char *)username->value : NULL,
						password ? (char *)password->value : NULL,
						rtsp_quirk, notify_changes,
						doaudio,
						(apt && apt->value) ? atoi(apt->value) : -1,
						(char *)audiocodec,
						afmtp ? (char *)afmtp->value : NULL,
						dovideo,
						(vpt && vpt->value) ? atoi(vpt->value) : -1,
						(char *)videocodec,
						vfmtp ? (char *)vfmtp->value : NULL,
						bufferkf_ms, bufferkf_bytes,
						iface && iface->value ? &iface_value : NULL,
						(threads && threads->value) ? atoi(threads->value) : 0,
						((reconnect_delay && reconnect_delay->value) ? atoi(reconnect_delay->value) : JANUS_STREAMING_DEFAULT_RECONNECT_DELAY) * G_USEC_PER_SEC,
						((session_timeout && session_timeout->value) ? atoi(session_timeout->value) : JANUS_STREAMING_DEFAULT_SESSION_TIMEOUT) * G_USEC_PER_SEC,
						((rtsp_timeout && rtsp_timeout->value) ? atoi(rtsp_timeout->value) : JANUS_STREAMING_DEFAULT_CURL_TIMEOUT),
						((rtsp_conn_timeout && rtsp_conn_timeout->value) ? atoi(rtsp_conn_timeout->value) : JANUS_STREAMING_DEFAULT_CURL_CONNECT_TIMEOUT),
						error_on_failure)) == NULL) {
					JANUS_LOG(LOG_ERR, "Error creating 'rtsp' mountpoint '%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 mountpoint 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, "  ::: [%s][%s] %s (%s, %s, %s, pin: %s)\n", mp->id_str, 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 : "??");
		g_error_free(error);
		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;
	g_hash_table_destroy(mountpoints_temp);
	mountpoints_temp = 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 */
	janus_mutex_init(&session->mutex);
	g_atomic_int_set(&session->started, 0);
	g_atomic_int_set(&session->paused, 0);
	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", string_ids ? json_string(mp->id_str) : 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);
		if(mp->streaming_source == janus_streaming_source_file) {
			janus_streaming_file_source *source = mp->source;
			json_t *media = json_object();
			json_object_set_new(media, "type", json_string("audio"));
			json_object_set_new(media, "filename", json_string(source->filename));
			json_object_set_new(info, "media", media);
		} else if(mp->streaming_source == janus_streaming_source_rtp) {
			json_t *media = json_array();
			GList *temp = session->streams;
			while(temp) {
				janus_streaming_session_stream *s = (janus_streaming_session_stream *)temp->data;
				janus_streaming_rtp_source_stream *stream = s->stream;
				json_t *info = json_object();
				json_object_set_new(info, "type", json_string(janus_streaming_media_str(stream->type)));
				json_object_set_new(info, "mindex", json_integer(s->mindex));
				json_object_set_new(info, "mid", json_string(stream->mid));
				if(stream->simulcast) {
					json_t *simulcast = json_object();
					json_object_set_new(simulcast, "substream", json_integer(s->sim_context.substream));
					json_object_set_new(simulcast, "substream-target", json_integer(s->sim_context.substream_target));
					json_object_set_new(simulcast, "temporal-layer", json_integer(s->sim_context.templayer));
					json_object_set_new(simulcast, "temporal-layer-target", json_integer(s->sim_context.templayer_target));
					if(s->sim_context.drop_trigger > 0)
						json_object_set_new(simulcast, "fallback", json_integer(s->sim_context.drop_trigger));
					json_object_set_new(info, "simulcast", simulcast);
				}
				if(stream->svc) {
					json_t *svc = json_object();
					json_object_set_new(svc, "spatial-layer", json_integer(s->spatial_layer));
					json_object_set_new(svc, "target-spatial-layer", json_integer(s->target_spatial_layer));
					json_object_set_new(svc, "temporal-layer", json_integer(s->temporal_layer));
					json_object_set_new(svc, "target-temporal-layer", json_integer(s->target_temporal_layer));
					json_object_set_new(info, "svc", svc);
				}
				if(stream->type == JANUS_STREAMING_MEDIA_VIDEO && session->playoutdelay_ext) {
					json_t *pd = json_object();
					json_object_set_new(pd, "min-delay", json_integer(s->min_delay));
					json_object_set_new(pd, "max-delay", json_integer(s->max_delay));
					json_object_set_new(info, "playout-delay", pd);
				}
				if(session->abscapturetime_src_ext_id > 0)
					json_object_set_new(info, "abs-capture-time-src-ext-id", json_integer(session->abscapturetime_src_ext_id));
				json_array_append_new(media, info);
				temp = temp->next;
			}
			json_object_set_new(info, "media", media);
		}
		janus_refcount_decrease(&mp->ref);
	}
	if(session->e2ee)
		json_object_set_new(info, "e2ee", json_true());
	json_object_set_new(info, "hangingup", json_integer(g_atomic_int_get(&session->hangingup)));
	json_object_set_new(info, "started", json_integer(g_atomic_int_get(&session->started)));
	json_object_set_new(info, "dataready", json_integer(g_atomic_int_get(&session->dataready)));
	json_object_set_new(info, "paused", json_integer(g_atomic_int_get(&session->paused)));
	json_object_set_new(info, "stopping", json_integer(g_atomic_int_get(&session->stopping)));
	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")) {
		JANUS_LOG(LOG_VERB, "Request for the list of mountpoints\n");
		gboolean lock_mp_list = TRUE;
		if(admin_key != NULL) {
			json_t *admin_key_json = json_object_get(root, "admin_key");
			/* Verify admin_key if it was provided */
			if(admin_key_json != NULL && json_is_string(admin_key_json) && strlen(json_string_value(admin_key_json)) > 0) {
				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;
				} else {
					lock_mp_list = FALSE;
				}
			}
		}
		json_t *list = json_array();
		/* 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 && lock_mp_list) {
				/* Skip private stream if no valid admin_key was provided */
				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", string_ids ? json_string(mp->id_str) : json_integer(mp->id));
			json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
			json_object_set_new(ml, "description", json_string(mp->description));
			if(mp->metadata) {
				json_object_set_new(ml, "metadata", json_string(mp->metadata));
			}
			json_object_set_new(ml, "enabled", mp->enabled ? json_true() : json_false());
			if(mp->streaming_source == janus_streaming_source_rtp) {
				janus_streaming_rtp_source *source = mp->source;
				gint64 now = janus_get_monotonic_time();
				json_t *media = json_array();
				GList *temp = source->media;
				while(temp) {
					json_t *info = json_object();
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					json_object_set_new(info, "mid", json_string(stream->mid));
					json_object_set_new(info, "type", json_string(janus_streaming_media_str(stream->type)));
					json_object_set_new(info, "label", json_string(stream->label));
					if(stream->msid && stream->mstid) {
						char msid[150];
						g_snprintf(msid, sizeof(msid), "%s %s", stream->msid, stream->mstid);
						json_object_set_new(info, "msid", json_string(msid));
					}
					if(stream->fd[0] != -1 || stream->fd[1] != -1 || stream->fd[2] != -1)
						json_object_set_new(info, "age_ms", json_integer((now - stream->last_received[0]) / 1000));
					json_array_append_new(media, info);
					temp = temp->next;
				}
				json_object_set_new(ml, "media", media);
			}
			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 */
		if(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstr_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 = 0;
		char id_num[30], *id_value_str = NULL;
		if(!string_ids) {
			id_value = json_integer_value(id);
			g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
			id_value_str = id_num;
		} else {
			id_value_str = (char *)json_string_value(id);
		}
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
			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", string_ids ? json_string(mp->id_str) : 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(mp->metadata)
			json_object_set_new(ml, "metadata", json_string(mp->metadata));
		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(admin)
			json_object_set_new(ml, "viewers", json_integer(mp->viewers ? g_list_length(mp->viewers) : 0));
		json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
		/* Add details on all the media streams in this mountpoint */
		json_t *media = json_array();
		json_object_set_new(ml, "media", media);
		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));
			json_t *info = json_object();
			json_object_set_new(info, "type", json_string("audio"));
			if(source->codecs.pt != -1)
				json_object_set_new(info, "pt", json_integer(source->codecs.pt));
			if(source->codecs.audio_codec != JANUS_AUDIOCODEC_NONE) {
				const char *codec = janus_audiocodec_name(source->codecs.audio_codec);
				if(codec != NULL) {
					json_object_set_new(info, "codec", json_string(codec));
					json_object_set_new(info, "rtpmap", json_string(janus_sdp_get_codec_rtpmap(codec)));
				}
			}
			if(source->codecs.fmtp)
				json_object_set_new(info, "fmtp", json_string(source->codecs.fmtp));
			json_array_append_new(media, info);
		} else if(mp->streaming_source == janus_streaming_source_rtp) {
			janus_streaming_rtp_source *source = mp->source;
			/* Global stuff first */
			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));
					if(source->rtsp_quirk)
						json_object_set_new(ml, "rtsp_quirk", json_true());
				}
			}
#endif
			if(source->is_srtp) {
				json_object_set_new(ml, "srtp", json_true());
			}
			if(source->rtp_collision > 0)
				json_object_set_new(ml, "collision", json_integer(source->rtp_collision));
			if(source->bufferkf_ms > 0) {
				json_object_set_new(ml, "bufferkf_ms", json_integer(source->bufferkf_ms));
			}
			if(source->bufferkf_bytes > 0) {
				json_object_set_new(ml, "bufferkf_bytes", json_integer(source->bufferkf_bytes));
			}
			if(mp->helper_threads > 0)
				json_object_set_new(ml, "threads", json_integer(mp->helper_threads));
			/* Iterate on media now */
			GList *temp = source->media;
			while(temp) {
				janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
				json_t *info = json_object();
				json_object_set_new(info, "mindex", json_integer(stream->mindex));
				json_object_set_new(info, "type", json_string(janus_streaming_media_str(stream->type)));
				json_object_set_new(info, "mid", json_string(stream->mid));
				json_object_set_new(info, "label", json_string(stream->label));
				if(stream->msid && stream->mstid) {
					char msid[150];
					g_snprintf(msid, sizeof(msid), "%s %s", stream->msid, stream->mstid);
					json_object_set_new(info, "msid", json_string(msid));
				}
				if(stream->codecs.pt != -1)
					json_object_set_new(info, "pt", json_integer(stream->codecs.pt));
				if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE) {
					const char *codec = janus_audiocodec_name(stream->codecs.audio_codec);
					if(codec != NULL) {
						json_object_set_new(info, "codec", json_string(codec));
						json_object_set_new(info, "rtpmap", json_string(janus_sdp_get_codec_rtpmap(codec)));
					}
				}
				if(stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE) {
					const char *codec = janus_videocodec_name(stream->codecs.video_codec);
					if(codec != NULL) {
						json_object_set_new(info, "codec", json_string(codec));
						json_object_set_new(info, "rtpmap", json_string(janus_sdp_get_codec_rtpmap(codec)));
					}
				}
				if(stream->codecs.fmtp)
					json_object_set_new(info, "fmtp", json_string(stream->codecs.fmtp));
				if(stream->simulcast) {
					json_object_set_new(info, "videosimulcast", json_true());
				}
				if(stream->svc) {
					json_object_set_new(info, "videosvc", json_true());
				}
				if(stream->skew)
					json_object_set_new(info, "skew_compensation", json_true());
				if(admin) {
					if(stream->host)
						json_object_set_new(ml, "host", json_string(stream->host));
					json_object_set_new(info, "port", json_integer(stream->port[0]));
					if(stream->rtcp_port > -1)
						json_object_set_new(info, "rtcpport", json_integer(stream->rtcp_port));
					if(stream->port[1] > -1)
						json_object_set_new(info, "port2", json_integer(stream->port[1]));
					if(stream->port[2] > -1)
						json_object_set_new(info, "port3", json_integer(stream->port[2]));
				}
				if(stream->type == JANUS_STREAMING_MEDIA_DATA)
					json_object_set_new(info, "datatype", json_string(stream->textdata ? "text" : "binary"));
				if(stream->fd[0] != -1 || stream->fd[1] != -1 || stream->fd[2] != -1)
					json_object_set_new(info, "age_ms", json_integer((now - stream->last_received[0]) / 1000));
				janus_mutex_lock(&source->rec_mutex);
				if(admin && stream->rc && stream->rc->filename)
					json_object_set_new(info, "recording", json_string(stream->rc->filename));
				janus_mutex_unlock(&source->rec_mutex);
				json_array_append_new(media, info);
				temp = temp->next;
			}
		}
		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(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, idopt_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstropt_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... %d (%s)\n",
				errno, g_strerror(errno));
		}

		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;
		}
		json_t *id = json_object_get(root, "id");
		/* Check if an ID has been provided, or if we need to generate one ourselves */
		janus_mutex_lock(&mountpoints_mutex);
		guint64 mpid = string_ids ? 0 : json_integer_value(id);
		char *mpid_str = (char *)(string_ids ? json_string_value(id) : NULL);
		if((!string_ids && mpid > 0) || (string_ids && mpid_str != NULL)) {
			/* Make sure the provided ID isn't already in use */
			if(g_hash_table_lookup(mountpoints, string_ids ? (gpointer)mpid_str : (gpointer)&mpid) != NULL ||
					g_hash_table_lookup(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid) != NULL) {
				janus_mutex_unlock(&mountpoints_mutex);
				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;
			}
		} else if(!string_ids && mpid == 0) {
			/* Generate a unique numeric ID */
			JANUS_LOG(LOG_VERB, "Missing numeric id, will generate a random one...\n");
			while(mpid == 0) {
				mpid = janus_random_uint64();
				if(g_hash_table_lookup(mountpoints, &mpid) != NULL ||
						g_hash_table_lookup(mountpoints_temp, &mpid) != NULL) {
					/* ID already in use, try another one */
					mpid = 0;
				}
			}
		} else if(string_ids && mpid_str == NULL) {
			/* Generate a unique alphanumeric ID */
			JANUS_LOG(LOG_VERB, "Missing alphanumeric id, will generate a random one...\n");
			while(mpid_str == 0) {
				mpid_str = janus_random_uuid();
				if(g_hash_table_lookup(mountpoints, mpid_str) != NULL ||
						g_hash_table_lookup(mountpoints_temp, mpid_str) != NULL) {
					/* ID already in use, try another one */
					g_free(mpid_str);
					mpid_str = NULL;
				}
			}
		}
		g_hash_table_insert(mountpoints_temp,
			string_ids ? (gpointer)g_strdup(mpid_str) : (gpointer)janus_uint64_dup(mpid),
			GUINT_TO_POINTER(TRUE));
		janus_mutex_unlock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = NULL;
		gboolean legacy = FALSE;
		if(!strcasecmp(type_text, "rtp")) {
			/* 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) {
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *md = json_object_get(root, "metadata");
			json_t *is_private = json_object_get(root, "is_private");
			json_t *rtpcollision = json_object_get(root, "collision");
			json_t *vkf_ms = json_object_get(root, "bufferkf_ms");
			json_t *vkf_bytes = json_object_get(root, "bufferkf_bytes");
			json_t *threads = json_object_get(root, "threads");
			json_t *ssuite = json_object_get(root, "srtpsuite");
			json_t *scrypto = json_object_get(root, "srtpcrypto");
			json_t *e2ee = json_object_get(root, "e2ee");
			json_t *pd = json_object_get(root, "playoutdelay_ext");
			json_t *abscaptime_src_id = json_object_get(root, "abscapturetime_src_ext_id");
			if(abscaptime_src_id && (json_integer_value(abscaptime_src_id) < 1 || json_integer_value(abscaptime_src_id) > 14)) {
				JANUS_LOG(LOG_ERR, "Invalid element (abscaptime_src_id must be an integer between 1 and 14)\n");
				error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
				g_snprintf(error_cause, 512, "Invalid element (abscaptime_src_id must be an integer between 1 and 14)");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			if(vkf_ms && json_integer_value(vkf_ms) > UINT16_MAX) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, invalid bufferkf_ms value...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtp' stream, invalid bufferkf_ms value...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			uint16_t bufferkf_ms = vkf_ms ? json_integer_value(vkf_ms) : 0;
			if(vkf_bytes && json_integer_value(vkf_bytes) > UINT32_MAX) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, invalid bufferkf_bytes value...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtp' stream, invalid bufferkf_bytes value...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			uint32_t bufferkf_bytes = vkf_bytes ? json_integer_value(vkf_bytes) : 0;
			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...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *media = json_object_get(root, "media");
			GList *streams = NULL;
			/* How are we adding media? */
			if(media != NULL) {
				uint16_t s_bufferkf_ms = 0;
				uint32_t s_bufferkf_bytes = 0;
				/* We're using the new media-based configuration, iterate on all media objects */
				if(json_array_size(media) == 0) {
					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...");
					janus_mutex_lock(&mountpoints_mutex);
					g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					goto prepare_response;
				}
				size_t i = 0;
				for(i=0; i<json_array_size(media); i++) {
					json_t *m = json_array_get(media, i);
					JANUS_VALIDATE_JSON_OBJECT(m, rtp_media_parameters,
						error_code, error_cause, TRUE,
						JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
					if(error_code != 0) {
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
					janus_network_address iface;
					uint16_t port = 0, port2 = 0, port3 = 0;
					uint16_t rtcpport = 0;
					uint8_t pt = 0;
					char *mtype = NULL, *mid = NULL, *label = NULL, *msid = NULL, *codec = NULL, *fmtp = NULL, *sps = NULL, *mcast = NULL, *miface = NULL;
					gboolean doskew = FALSE, simulcast = FALSE, dosvc = FALSE, textdata = TRUE, buffermsg = FALSE;
					json_t *jmtype = json_object_get(m, "type");
					mtype = (char *)json_string_value(jmtype);
					if(strcasecmp(mtype, "audio") && strcasecmp(mtype, "video") && strcasecmp(mtype, "data")) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', unsupported media type...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, "Unsupported media type in media stream");
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
					/* We need mid and label for addressing this stream on the client side */
					json_t *jmid = json_object_get(m, "mid");
					mid = (char *)json_string_value(jmid);
					json_t *jlabel = json_object_get(m, "label");
					label = (char *)json_string_value(jlabel);
					json_t *jmsid = json_object_get(m, "msid");
					msid = (char *)json_string_value(jmsid);
					json_t *jmcast = json_object_get(m, "mcast");
					mcast = (char *)json_string_value(jmcast);
					json_t *jport = json_object_get(m, "port");
					port = json_integer_value(jport);
					json_t *jrtcpport = json_object_get(m, "rtcpport");
					if(jrtcpport)
						rtcpport = json_integer_value(jrtcpport);
					json_t *jpt = json_object_get(m, "pt");
					pt = json_integer_value(jpt);
					json_t *jcodec = json_object_get(m, "codec");
					codec = (char *)json_string_value(jcodec);
					if(codec == NULL) {
						/* No codec property, check the deprecated rtpmap */
						json_t *jrtpmap = json_object_get(m, "rtpmap");
						if(jrtpmap)
							codec = (char *)janus_sdp_get_rtpmap_codec(json_string_value(jrtpmap));
					}
					json_t *jfmtp = json_object_get(m, "fmtp");
					fmtp = (char *)json_string_value(jfmtp);
					json_t *jsps = json_object_get(m, "h264sps");
					sps = (char *)json_string_value(jsps);
					json_t *jiface = json_object_get(m, "iface");
					miface = (char *)json_string_value(jiface);
					if(jiface) {
						if(janus_network_lookup_interface(ifas, miface, &iface) != 0) {
							JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', invalid network interface configuration for media 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 media stream" : "Unable to query network device information");
							g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
							janus_mutex_lock(&mountpoints_mutex);
							g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
							janus_mutex_unlock(&mountpoints_mutex);
							goto prepare_response;
						}
					} else {
						janus_network_address_nullify(&iface);
					}
					json_t *jskew = json_object_get(m, "skew");
					doskew = jskew ? json_is_true(jskew) : FALSE;
					s_bufferkf_ms = 0;
					s_bufferkf_bytes = 0;
					if(!strcasecmp(mtype, "video")) {
						json_t *vkf = json_object_get(m, "bufferkf");
						if(json_is_true(vkf)) {
							JANUS_LOG(LOG_WARN, "The bufferkf property has been deprecated, please refer to bufferkf_ms and/or bufferkf_bytes\n");
						}
						s_bufferkf_ms = bufferkf_ms;
						s_bufferkf_bytes = bufferkf_bytes;
						json_t *vsc = json_object_get(m, "simulcast");
						simulcast = vsc ? json_is_true(vsc) : FALSE;
						if(simulcast && (s_bufferkf_ms > 0 || s_bufferkf_bytes > 0)) {
							/* FIXME We'll need to take care of this */
							JANUS_LOG(LOG_WARN, "Simulcasting enabled, so disabling buffering of keyframes for this stream\n");
							s_bufferkf_ms = 0;
							s_bufferkf_bytes = 0;
						}
						json_t *videoport2 = json_object_get(m, "port2");
						port2 = json_integer_value(videoport2);
						json_t *videoport3 = json_object_get(m, "port3");
						port3 = json_integer_value(videoport3);
						json_t *vsvc = json_object_get(m, "svc");
						dosvc = vsvc ? json_is_true(vsvc) : FALSE;
					} else if(!strcasecmp(mtype, "data")) {
						json_t *dbm = json_object_get(root, "buffermsg");
						buffermsg = dbm ? json_is_true(dbm) : FALSE;
						json_t *dt = json_object_get(root, "datatype");
						if(dt) {
							const char *datatype = (const char *)json_string_value(dt);
							if(!strcasecmp(datatype, "text"))
								textdata = TRUE;
							else if(!strcasecmp(datatype, "binary"))
								textdata = FALSE;
							else {
								JANUS_LOG(LOG_ERR, "Invalid element (datatype can only be text or binary)\n");
								error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
								g_snprintf(error_cause, 512, "Invalid element (datatype can only be text or binary)");
								g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
								janus_mutex_lock(&mountpoints_mutex);
								g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
								janus_mutex_unlock(&mountpoints_mutex);
								goto prepare_response;
							}
						}
					}
					/* Create the source stream */
					janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
						name ? (char *)json_string_value(name) : NULL, g_list_length(streams),
						mtype, mid, label ? label : mtype, msid, mcast, miface, &iface,
						port, port2, port3, jrtcpport != NULL, rtcpport,
						pt, codec, fmtp, sps,
						doskew, bufferkf_ms, bufferkf_bytes, simulcast, dosvc, textdata, buffermsg);
					if(stream == NULL) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', error creating data source stream...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, "Can't add 'rtp' stream, error creating data source stream");
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
					/* Add to the list of streams */
					streams = g_list_append(streams, stream);
				}
			} else {
				/* If we got here, we create a mountpoint the "old" way */
				legacy = TRUE;
				JANUS_LOG(LOG_WARN, "Deprecated mountpoint 'create' API: please start looking into the new one for the future\n");
				janus_network_address audio_iface, video_iface, data_iface;
				json_t *audio = json_object_get(root, "audio");
				json_t *video = json_object_get(root, "video");
				json_t *data = json_object_get(root, "data");
				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...");
					janus_mutex_lock(&mountpoints_mutex);
					g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					goto prepare_response;
				}
				uint16_t aport = 0;
				uint16_t artcpport = 0;
				uint8_t apt = 0;
				char *acodec = NULL, *afmtp = NULL, *amcast = NULL, *amiface = 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) {
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						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");
					apt = json_integer_value(audiopt);
					json_t *audiocodec = json_object_get(root, "audiocodec");
					acodec = (char *)json_string_value(audiocodec);
					if(acodec == NULL) {
						/* No audiocodec property, check the deprecated audiortpmap */
						json_t *audiortpmap = json_object_get(root, "audiortpmap");
						if(audiortpmap)
							acodec = (char *)janus_sdp_get_rtpmap_codec(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");
					amiface = (char *)json_string_value(aiface);
					if(aiface) {
						if(janus_network_lookup_interface(ifas, amiface, &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");
							janus_mutex_lock(&mountpoints_mutex);
							g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
							janus_mutex_unlock(&mountpoints_mutex);
							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;
					/* Create the audio source stream */
					janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
						name ? (char *)json_string_value(name) : NULL, g_list_length(streams),
						"audio", "a", "audio", NULL,
						amcast, amiface, &audio_iface,
						aport, 0, 0, audiortcpport != NULL, artcpport,
						apt, acodec, afmtp, NULL, doaskew, 0, 0, FALSE, FALSE, FALSE, FALSE);
					if(stream == NULL) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', error creating audio source stream...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, "Can't add 'rtp' stream, error creating audio source stream");
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
					/* Add to the list of streams */
					streams = g_list_append(streams, stream);
				}
				uint16_t vport = 0, vport2 = 0, vport3 = 0;
				uint16_t vrtcpport = 0;
				uint8_t vpt = 0;
				char *vcodec = NULL, *vfmtp = NULL, *vsps = NULL, *vmcast = NULL, *vmiface = NULL;
				gboolean 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) {
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						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");
					vpt = json_integer_value(videopt);
					json_t *videocodec = json_object_get(root, "videocodec");
					vcodec = (char *)json_string_value(videocodec);
					if(vcodec == NULL) {
						/* No videocodec property, check the deprecated videortpmap */
						json_t *videortpmap = json_object_get(root, "videortpmap");
						if(videortpmap)
							vcodec = (char *)janus_sdp_get_rtpmap_codec(json_string_value(videortpmap));
					}
					json_t *videofmtp = json_object_get(root, "videofmtp");
					vfmtp = (char *)json_string_value(videofmtp);
					json_t *h264sps = json_object_get(root, "h264sps");
					vsps = (char *)json_string_value(h264sps);
					json_t *vkf = json_object_get(root, "videobufferkf");
					if(json_is_true(vkf)) {
						JANUS_LOG(LOG_WARN, "The videobufferkf property has been deprecated, please refer to bufferkf_ms and/or bufferkf_bytes\n");
					}
					json_t *vsc = json_object_get(root, "videosimulcast");
					simulcast = vsc ? json_is_true(vsc) : FALSE;
					if(simulcast && (bufferkf_ms > 0 || bufferkf_bytes > 0)) {
						/* FIXME We'll need to take care of this */
						JANUS_LOG(LOG_WARN, "Simulcasting enabled, so disabling buffering of keyframes for this stream\n");
						bufferkf_ms = 0;
						bufferkf_bytes = 0;
					}
					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");
					vmiface = (char *)json_string_value(viface);
					if(viface) {
						if(janus_network_lookup_interface(ifas, vmiface, &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");
							janus_mutex_lock(&mountpoints_mutex);
							g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
							janus_mutex_unlock(&mountpoints_mutex);
							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;
					/* Create the video source stream */
					janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
						name ? (char *)json_string_value(name) : NULL,
						g_list_length(streams),
						"video", "v", "video", NULL,
						vmcast, vmiface, &video_iface,
						vport, vport2, vport3, videortcpport != NULL, vrtcpport,
						vpt, vcodec, vfmtp, vsps,
						dovskew, bufferkf_ms, bufferkf_bytes, simulcast, dosvc, FALSE, FALSE);
					if(stream == NULL) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', error creating video source stream...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, "Can't add 'rtp' stream, error creating video source stream");
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
					/* Add to the list of streams */
					streams = g_list_append(streams, stream);
				}
				uint16_t dport = 0;
				gboolean textdata = TRUE, buffermsg = FALSE;
				char *dmcast = NULL, *dmiface = NULL;
				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) {
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
#ifdef HAVE_SCTP
					json_t *datamcast = json_object_get(root, "datamcast");
					dmcast = (char *)json_string_value(datamcast);
					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 *dt = json_object_get(root, "datatype");
					if(dt) {
						const char *datatype = (const char *)json_string_value(dt);
						if(!strcasecmp(datatype, "text"))
							textdata = TRUE;
						else if(!strcasecmp(datatype, "binary"))
							textdata = FALSE;
						else {
							JANUS_LOG(LOG_ERR, "Invalid element (datatype can only be text or binary)\n");
							error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
							g_snprintf(error_cause, 512, "Invalid element (datatype can only be text or binary)");
							janus_mutex_lock(&mountpoints_mutex);
							g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
							janus_mutex_unlock(&mountpoints_mutex);
							goto prepare_response;
						}
					}
					json_t *diface = json_object_get(root, "dataiface");
					dmiface = (char *)json_string_value(diface);
					if(diface) {
						if(janus_network_lookup_interface(ifas, dmiface, &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");
							janus_mutex_lock(&mountpoints_mutex);
							g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
							janus_mutex_unlock(&mountpoints_mutex);
							goto prepare_response;
						}
					} else {
						janus_network_address_nullify(&data_iface);
					}
					/* Create the data source stream */
					janus_streaming_rtp_source_stream *stream = janus_streaming_create_rtp_source_stream(
						name ? (char *)json_string_value(name) : NULL,
						g_list_length(streams),
						"data", "d", "data", NULL,
						dmcast, dmiface, &data_iface,
						dport, 0, 0, FALSE, 0,
						0, NULL, NULL, NULL, FALSE, 0, 0, FALSE, FALSE, textdata, buffermsg);
					if(stream == NULL) {
						JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream '%s', error creating data source stream...\n", (const char *)json_string_value(name));
						error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
						g_snprintf(error_cause, 512, "Can't add 'rtp' stream, error creating data source stream");
						g_list_free_full(streams, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
					/* Add to the list of streams */
					streams = g_list_append(streams, stream);
#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...");
					janus_mutex_lock(&mountpoints_mutex);
					g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
					janus_mutex_unlock(&mountpoints_mutex);
					goto prepare_response;
#endif
				}
				JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled");
			}
			mp = janus_streaming_create_rtp_source(
					mpid, mpid_str,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					md ? (char *)json_string_value(md) : NULL,
					streams,
					ssuite ? json_integer_value(ssuite) : 0,
					scrypto ? (char *)json_string_value(scrypto) : NULL,
					threads ? json_integer_value(threads) : 0,
					rtpcollision ? json_integer_value(rtpcollision) : 0,
					bufferkf_ms, bufferkf_bytes,
					e2ee ? json_is_true(e2ee) : FALSE,
					pd ? json_is_true(pd) : FALSE,
					abscaptime_src_id ? json_integer_value(abscaptime_src_id) : 0);
			janus_mutex_lock(&mountpoints_mutex);
			g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
			janus_mutex_unlock(&mountpoints_mutex);
			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-based 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) {
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *md = json_object_get(root, "metadata");
			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;
			uint8_t apt = 0;
			char *acodec = NULL, *afmtp = NULL;
			if(doaudio) {
				json_t *audiopt = json_object_get(root, "audiopt");
				apt = json_integer_value(audiopt);
				json_t *audiocodec = json_object_get(root, "audiocodec");
				acodec = (char *)json_string_value(audiocodec);
				if(acodec == NULL) {
					/* No audiocodec property, check the deprecated audiortpmap */
					json_t *audiortpmap = json_object_get(root, "audiortpmap");
					if(audiortpmap)
						acodec = (char *)janus_sdp_get_rtpmap_codec(json_string_value(audiortpmap));
				}
				json_t *audiofmtp = json_object_get(root, "audiofmtp");
				afmtp = (char *)json_string_value(audiofmtp);
			}
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			/* We only support audio for file-based streaming at the moment: for streaming
			 * files using other codecs/formats an external tools should feed us RTP instead */
			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...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			char *filename = (char *)json_string_value(file);
			if(strstr(filename, "../") != NULL) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, can't use relative paths\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'live' stream, can't use relative paths");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
#ifdef HAVE_LIBOGG
			if(!janus_streaming_check_extension(filename, ".opus") &&
					!janus_streaming_check_extension(filename, ".alaw") &&
					!janus_streaming_check_extension(filename, ".mulaw")) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, unsupported format (we only support Opus and raw mu-Law/a-Law files right now)\n");
#else
			if(!janus_streaming_check_extension(filename, ".alaw") &&
					!janus_streaming_check_extension(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");
#endif
				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)");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				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);
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			fclose(audiofile);
			mp = janus_streaming_create_file_source(
					mpid, mpid_str,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					md ? (char *)json_string_value(md) : NULL,
					filename, TRUE,
					doaudio, apt, acodec, afmtp, dovideo);
			janus_mutex_lock(&mountpoints_mutex);
			g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
			janus_mutex_unlock(&mountpoints_mutex);
			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")) {
			/* File-based 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) {
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *md = json_object_get(root, "metadata");
			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;
			uint8_t apt = 0;
			char *acodec = NULL, *afmtp = NULL;
			if(doaudio) {
				json_t *audiopt = json_object_get(root, "audiopt");
				apt = json_integer_value(audiopt);
				json_t *audiocodec = json_object_get(root, "audiocodec");
				acodec = (char *)json_string_value(audiocodec);
				if(acodec == NULL) {
					/* No audiocodec property, check the deprecated audiortpmap */
					json_t *audiortpmap = json_object_get(root, "audiortpmap");
					if(audiortpmap)
						acodec = (char *)janus_sdp_get_rtpmap_codec(json_string_value(audiortpmap));
				}
				json_t *audiofmtp = json_object_get(root, "audiofmtp");
				afmtp = (char *)json_string_value(audiofmtp);
			}
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			/* We only support audio for file-based streaming at the moment: for streaming
			 * files using other codecs/formats an external tools should feed us RTP instead */
			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...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			char *filename = (char *)json_string_value(file);
			if(strstr(filename, "../") != NULL) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, can't use relative paths\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'live' stream, can't use relative paths");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
#ifdef HAVE_LIBOGG
			if(!janus_streaming_check_extension(filename, ".opus") &&
					!janus_streaming_check_extension(filename, ".alaw") &&
					!janus_streaming_check_extension(filename, ".mulaw")) {
				JANUS_LOG(LOG_ERR, "Can't add 'live' stream, unsupported format (we only support Opus and raw mu-Law/a-Law files right now)\n");
#else
			if(!janus_streaming_check_extension(filename, ".alaw") &&
					!janus_streaming_check_extension(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");
#endif
				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)");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				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);
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			fclose(audiofile);
			mp = janus_streaming_create_file_source(
					mpid, mpid_str,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					md ? (char *)json_string_value(md) : NULL,
					filename, FALSE,
					doaudio, apt, acodec, afmtp, dovideo);
			janus_mutex_lock(&mountpoints_mutex);
			g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
			janus_mutex_unlock(&mountpoints_mutex);
			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) {
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			/* RTSP source*/
			janus_network_address multicast_iface;
			json_t *name = json_object_get(root, "name");
			json_t *desc = json_object_get(root, "description");
			json_t *md = json_object_get(root, "metadata");
			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 *audiocodec = json_object_get(root, "audiocodec");
			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 *videocodec = json_object_get(root, "videocodec");
			json_t *videortpmap = json_object_get(root, "videortpmap");
			json_t *videofmtp = json_object_get(root, "videofmtp");
			json_t *vkf = json_object_get(root, "videobufferkf");
			json_t *vkf_ms = json_object_get(root, "bufferkf_ms");
			json_t *vkf_bytes = json_object_get(root, "bufferkf_bytes");
			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 *quirk = json_object_get(root, "rtsp_quirk");
			json_t *iface = json_object_get(root, "rtspiface");
			json_t *threads = json_object_get(root, "threads");
			json_t *failerr = json_object_get(root, "rtsp_failcheck");
			json_t *reconnect_delay = json_object_get(root, "rtsp_reconnect_delay");
			json_t *session_timeout = json_object_get(root, "rtsp_session_timeout");
			json_t *rtsp_timeout = json_object_get(root, "rtsp_timeout");
			json_t *rtsp_conn_timeout = json_object_get(root, "rtsp_conn_timeout");
			json_t *rtsp_notify_changes = json_object_get(root, "rtsp_notify_changes");
			if(failerr == NULL)	/* For an old typo, we support the legacy syntax too */
				failerr = json_object_get(root, "rtsp_check");
			gboolean doaudio = audio ? json_is_true(audio) : FALSE;
			gboolean dovideo = video ? json_is_true(video) : FALSE;
			gboolean doquirk = quirk ? json_is_true(quirk) : FALSE;
			gboolean notify_changes = rtsp_notify_changes ? json_is_true(rtsp_notify_changes) : FALSE;
			gboolean error_on_failure = failerr ? json_is_true(failerr) : TRUE;
			if(json_is_true(vkf)) {
				JANUS_LOG(LOG_WARN, "The videobufferkf property has been deprecated, please refer to bufferkf_ms and/or bufferkf_bytes\n");
			}
			if(vkf_ms && json_integer_value(vkf_ms) > UINT16_MAX) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream, invalid bufferkf_ms value...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtsp' stream, invalid bufferkf_ms value...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			uint16_t bufferkf_ms = vkf_ms ? json_integer_value(vkf_ms) : 0;
			if(vkf_bytes && json_integer_value(vkf_bytes) > UINT32_MAX) {
				JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream, invalid bufferkf_bytes value...\n");
				error_code = JANUS_STREAMING_ERROR_CANT_CREATE;
				g_snprintf(error_cause, 512, "Can't add 'rtsp' stream, invalid bufferkf_bytes value...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			uint32_t bufferkf_bytes = vkf_bytes ? json_integer_value(vkf_bytes) : 0;
			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...");
				janus_mutex_lock(&mountpoints_mutex);
				g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
				janus_mutex_unlock(&mountpoints_mutex);
				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");
						janus_mutex_lock(&mountpoints_mutex);
						g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
						janus_mutex_unlock(&mountpoints_mutex);
						goto prepare_response;
					}
				} else {
					janus_network_address_nullify(&multicast_iface);
				}
			}
			char *acodec = (char *)json_string_value(audiocodec);
			if(acodec == NULL && audiortpmap) {
				/* No audiocodec property, check the deprecated audiortpmap */
				acodec = (char *)janus_sdp_get_rtpmap_codec(json_string_value(audiortpmap));
			}
			char *vcodec = (char *)json_string_value(videocodec);
			if(vcodec == NULL && videortpmap) {
				/* No videocodec property, check the deprecated videortpmap */
				vcodec = (char *)janus_sdp_get_rtpmap_codec(json_string_value(videortpmap));
			}
			mp = janus_streaming_create_rtsp_source(
					mpid, mpid_str,
					name ? (char *)json_string_value(name) : NULL,
					desc ? (char *)json_string_value(desc) : NULL,
					md ? (char *)json_string_value(md) : NULL,
					(char *)json_string_value(url),
					username ? (char *)json_string_value(username) : NULL,
					password ? (char *)json_string_value(password) : NULL,
					doquirk, notify_changes,
					doaudio, (audiopt ? json_integer_value(audiopt) : -1), acodec, (char *)json_string_value(audiofmtp),
					dovideo, (videopt ? json_integer_value(videopt) : -1), vcodec, (char *)json_string_value(videofmtp),
					bufferkf_ms, bufferkf_bytes,
					&multicast_iface, (threads ? json_integer_value(threads) : 0),
					((reconnect_delay ? json_integer_value(reconnect_delay) : JANUS_STREAMING_DEFAULT_RECONNECT_DELAY) * G_USEC_PER_SEC),
					((session_timeout ? json_integer_value(session_timeout) : JANUS_STREAMING_DEFAULT_SESSION_TIMEOUT) * G_USEC_PER_SEC),
					(rtsp_timeout ? json_integer_value(rtsp_timeout) : JANUS_STREAMING_DEFAULT_CURL_TIMEOUT),
					(rtsp_conn_timeout ? json_integer_value(rtsp_conn_timeout) : JANUS_STREAMING_DEFAULT_CURL_CONNECT_TIMEOUT),
					error_on_failure);
			janus_mutex_lock(&mountpoints_mutex);
			g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid);
			janus_mutex_unlock(&mountpoints_mutex);
			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 %s permanently in config file\n", mp->id_str);
			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));
			janus_config_add(config, c, janus_config_item_create("id", mp->id_str));
			janus_config_add(config, c, janus_config_item_create("description", mp->description));
			if(mp->metadata)
				janus_config_add(config, c, janus_config_item_create("metadata", mp->metadata));
			if(mp->is_private)
				janus_config_add(config, c, janus_config_item_create("is_private", "true"));
			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));
			/* Per type values */
			if(!strcasecmp(type_text, "rtp")) {
				/* We save using the new format, not the old deprecated one */
				janus_streaming_rtp_source *source = mp->source;
				if(source->rtp_collision > 0) {
					g_snprintf(value, BUFSIZ, "%d", source->rtp_collision);
					janus_config_add(config, c, janus_config_item_create("collision", value));
				}
				if(source->bufferkf_ms > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu16, source->bufferkf_ms);
					janus_config_add(config, c, janus_config_item_create("bufferkf_ms", value));
				}
				if(source->bufferkf_bytes > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu32, source->bufferkf_bytes);
					janus_config_add(config, c, janus_config_item_create("bufferkf_bytes", value));
				}
				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));
				}
				if(source->e2ee)
					janus_config_add(config, c, janus_config_item_create("e2ee", "true"));
				if(source->playoutdelay_ext)
					janus_config_add(config, c, janus_config_item_create("playoutdelay_ext", "true"));
				if(source->abscapturetime_src_ext_id > 0) {
					g_snprintf(value, BUFSIZ, "%d", source->abscapturetime_src_ext_id);
					janus_config_add(config, c, janus_config_item_create("abscapturetime_src_ext_id", value));
				}
				/* Iterate on all media streams */
				janus_config_array *media = janus_config_array_create("media");
				janus_config_add(config, c, media);
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					janus_config_category *m = janus_config_category_create(NULL);
					janus_config_add(config, media, m);
					janus_config_add(config, m, janus_config_item_create("type", janus_streaming_media_str(stream->type)));
					janus_config_add(config, m, janus_config_item_create("mid", stream->mid));
					janus_config_add(config, m, janus_config_item_create("label", stream->label));
					if(stream->msid && stream->mstid) {
						char msid[150];
						g_snprintf(msid, sizeof(msid), "%s %s", stream->msid, stream->mstid);
						janus_config_add(config, m, janus_config_item_create("msid", msid));
					}
					if(stream->port[0] > 0) {
						g_snprintf(value, BUFSIZ, "%d", stream->port[0]);
						janus_config_add(config, m, janus_config_item_create("port", value));
					}
					if(stream->rtcp_port > 0) {
						g_snprintf(value, BUFSIZ, "%d", stream->rtcp_port);
						janus_config_add(config, m, janus_config_item_create("rtcpport", value));
					}
					if(stream->codecs.pt >= 0) {
						g_snprintf(value, BUFSIZ, "%d", stream->codecs.pt);
						janus_config_add(config, m, janus_config_item_create("pt", value));
					}
					if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE || stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE) {
						if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE)
							janus_config_add(config, m, janus_config_item_create("codec", janus_audiocodec_name(stream->codecs.audio_codec)));
						else if(stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE)
							janus_config_add(config, m, janus_config_item_create("codec", janus_videocodec_name(stream->codecs.video_codec)));
						if(stream->codecs.fmtp)
							janus_config_add(config, m, janus_config_item_create("fmtp", stream->codecs.fmtp));
						if(stream->skew)
							janus_config_add(config, m, janus_config_item_create("skew", "true"));
					}
					if(stream->simulcast) {
						janus_config_add(config, m, janus_config_item_create("videosimulcast", "true"));
						if(stream->port[1]) {
							g_snprintf(value, BUFSIZ, "%d", stream->port[1]);
							janus_config_add(config, m, janus_config_item_create("port2", value));
						}
						if(stream->port[2]) {
							g_snprintf(value, BUFSIZ, "%d", stream->port[2]);
							janus_config_add(config, m, janus_config_item_create("port3", value));
						}
					}
					if(stream->svc)
						janus_config_add(config, m, janus_config_item_create("videosvc", "true"));
					if(stream->skew)
						janus_config_add(config, m, janus_config_item_create("skew", "true"));
					if(stream->mcast_str)
						janus_config_add(config, m, janus_config_item_create("mcast", stream->mcast_str));
					if(stream->iface_str)
						janus_config_add(config, m, janus_config_item_create("iface", stream->iface_str));
					if(stream->type == JANUS_STREAMING_MEDIA_DATA)
						janus_config_add(config, m, janus_config_item_create("datatype", stream->textdata ? "text" : "binary"));
					if(stream->buffermsg)
						janus_config_add(config, m, janus_config_item_create("databuffermsg", "true"));
					temp = temp->next;
				}
			} 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", "true"));
			} else if(!strcasecmp(type_text, "rtsp")) {
				janus_streaming_rtp_source *source = mp->source;
#ifdef HAVE_LIBCURL
				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));
				if(source->rtsp_quirk)
					janus_config_add(config, c, janus_config_item_create("rtsp_quirk", "true"));
#endif
				if(source->bufferkf_ms > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu16, source->bufferkf_ms);
					janus_config_add(config, c, janus_config_item_create("bufferkf_ms", value));
				}
				if(source->bufferkf_bytes > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu32, source->bufferkf_bytes);
					janus_config_add(config, c, janus_config_item_create("bufferkf_bytes", value));
				}
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					/* FIXME Should we support RTSP streams with multiple media? */
					if(stream->type == JANUS_STREAMING_MEDIA_AUDIO) {
						janus_config_add(config, c, janus_config_item_create("audio", "true"));
						if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE)
							janus_config_add(config, c, janus_config_item_create("audiocodec", janus_audiocodec_name(stream->codecs.audio_codec)));
						if(stream->codecs.fmtp)
							janus_config_add(config, c, janus_config_item_create("audiofmtp", stream->codecs.fmtp));
					} else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
						janus_config_add(config, c, janus_config_item_create("video", "true"));
						if(stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE)
							janus_config_add(config, c, janus_config_item_create("videocodec", janus_videocodec_name(stream->codecs.video_codec)));
						if(stream->codecs.fmtp)
							janus_config_add(config, c, janus_config_item_create("videofmtp", stream->codecs.fmtp));
					}
					temp = temp->next;
				}
				json_t *iface = json_object_get(root, "rtspiface");
				if(iface)
					janus_config_add(config, c, janus_config_item_create("rtspiface", json_string_value(iface)));
				if(mp->helper_threads > 0) {
					g_snprintf(value, BUFSIZ, "%d", mp->helper_threads);
					janus_config_add(config, c, janus_config_item_create("threads", value));
				}
			}
			/* 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", string_ids ? json_string(mp->id_str) : json_integer(mp->id));
		json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand"));
		json_object_set_new(ml, "description", json_string(mp->description));
		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;
			json_t *media = legacy ? NULL : json_array();
			GList *temp = source->media;
			while(temp) {
				janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
				if(!legacy) {
					/* Return the new format */
					json_t *info = json_object();
					json_object_set_new(info, "type", json_string(janus_streaming_media_str(stream->type)));
					json_object_set_new(info, "mid", json_string(stream->mid));
					if(stream->msid && stream->mstid) {
						char msid[150];
						g_snprintf(msid, sizeof(msid), "%s %s", stream->msid, stream->mstid);
						json_object_set_new(info, "msid", json_string(msid));
					}
					if(stream->fd[0] != -1) {
						if(stream->host)
							json_object_set_new(info, "host", json_string(stream->host));
						json_object_set_new(info, "port", json_integer(stream->port[0]));
					}
					if(stream->rtcp_fd != -1) {
						json_object_set_new(info, "rtcp_port", json_integer(stream->rtcp_port));
					}
					if(stream->fd[1] != -1) {
						json_object_set_new(info, "port_2", json_integer(stream->port[1]));
					}
					if(stream->fd[2] != -1) {
						json_object_set_new(info, "port_3", json_integer(stream->port[2]));
					}
					json_array_append_new(media, info);
				} else {
					/* Return the old format */
					if(stream->type == JANUS_STREAMING_MEDIA_AUDIO) {
						if(stream->fd[0] != -1) {
							if(stream->host)
								json_object_set_new(ml, "audio_host", json_string(stream->host));
							json_object_set_new(ml, "audio_port", json_integer(stream->port[0]));
						}
						if(stream->rtcp_fd != -1) {
							json_object_set_new(ml, "audio_rtcp_port", json_integer(stream->rtcp_port));
						}
					} else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
						if(stream->fd[0] != -1) {
							if(stream->host)
								json_object_set_new(ml, "video_host", json_string(stream->host));
							json_object_set_new(ml, "video_port", json_integer(stream->port[0]));
						}
						if(stream->rtcp_fd != -1) {
							json_object_set_new(ml, "video_rtcp_port", json_integer(stream->rtcp_port));
						}
						if(stream->fd[1] != -1) {
							json_object_set_new(ml, "video_port_2", json_integer(stream->port[1]));
						}
						if(stream->fd[2] != -1) {
							json_object_set_new(ml, "video_port_3", json_integer(stream->port[2]));
						}
					} else if(stream->type == JANUS_STREAMING_MEDIA_DATA) {
						if(stream->fd[0] != -1) {
							if(stream->host)
								json_object_set_new(ml, "data_host", json_string(stream->host));
							json_object_set_new(ml, "data_port", json_integer(stream->port[0]));
						}
					}
				}
				temp = temp->next;
			}
			if(!legacy)
				json_object_set_new(ml, "ports", media);
		}
		json_object_set_new(response, "stream", ml);
		if(legacy)
			json_object_set_new(response, "warning", json_string("deprecated_api"));
		/* 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", string_ids ? json_string(mp->id_str) : 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;
		if(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstr_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 *md = json_object_get(root, "new_metadata");
		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");
		json_t *edited_event = json_object_get(root, "edited_event");
		gboolean send_edited_event = edited_event ? json_is_true(edited_event) : FALSE;
		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 = 0;
		char id_num[30], *id_value_str = NULL;
		if(!string_ids) {
			id_value = json_integer_value(id);
			g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
			id_value_str = id_num;
		} else {
			id_value_str = (char *)json_string_value(id);
		}
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_ERR, "No such mountpoint (%s)\n", id_value_str);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint (%s)", id_value_str);
			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(md != NULL) {
			char *old_metadata = mp->metadata;
			char *new_metadata = g_strdup(json_string_value(md));
			mp->metadata = new_metadata;
			if(send_edited_event == TRUE && ((old_metadata == NULL && new_metadata == NULL) || (old_metadata != NULL && new_metadata != NULL && !strcmp(old_metadata, new_metadata))))
				send_edited_event = FALSE;
			g_free(old_metadata);
		}
		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) {
			char *old_pin = mp->pin;
			if(strlen(json_string_value(pin)) > 0) {
				char *new_pin = g_strdup(json_string_value(pin));
				mp->pin = new_pin;
			} else {
				mp->pin = NULL;
			}
			g_free(old_pin);
		}
		if(save) {
			/* This changes are permanent: save to the configuration file too
			 * FIXME: We should check if anything fails... */
			JANUS_LOG(LOG_VERB, "Saving edited mountpoint %s permanently in config file\n", mp->id_str);
			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("id", mp->id_str));
			janus_config_add(config, c, janus_config_item_create("description", mp->description));
			if(mp->metadata)
				janus_config_add(config, c, janus_config_item_create("metadata", mp->metadata));
			if(mp->is_private)
				janus_config_add(config, c, janus_config_item_create("is_private", "true"));
			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));
			/* Per type values */
			if(mp->streaming_source == janus_streaming_source_rtp && !((janus_streaming_rtp_source *)mp->source)->rtsp) {
				/* We save using the new format, not the old deprecated one */
				janus_streaming_rtp_source *source = mp->source;
				if(source->rtp_collision > 0) {
					g_snprintf(value, BUFSIZ, "%d", source->rtp_collision);
					janus_config_add(config, c, janus_config_item_create("collision", value));
				}
				if(source->bufferkf_ms > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu16, source->bufferkf_ms);
					janus_config_add(config, c, janus_config_item_create("bufferkf_ms", value));
				}
				if(source->bufferkf_bytes > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu32, source->bufferkf_bytes);
					janus_config_add(config, c, janus_config_item_create("bufferkf_bytes", value));
				}
				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));
				}
				if(source->e2ee)
					janus_config_add(config, c, janus_config_item_create("e2ee", "true"));
				if(source->playoutdelay_ext)
					janus_config_add(config, c, janus_config_item_create("playoutdelay_ext", "true"));
				if(source->abscapturetime_src_ext_id > 0) {
					g_snprintf(value, BUFSIZ, "%d", source->abscapturetime_src_ext_id);
					janus_config_add(config, c, janus_config_item_create("abscapturetime_src_ext_id", value));
				}
				/* Iterate on all media streams */
				janus_config_array *media = janus_config_array_create("media");
				janus_config_add(config, c, media);
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					janus_config_category *m = janus_config_category_create(NULL);
					janus_config_add(config, media, m);
					janus_config_add(config, m, janus_config_item_create("type", janus_streaming_media_str(stream->type)));
					janus_config_add(config, m, janus_config_item_create("mid", stream->mid));
					janus_config_add(config, m, janus_config_item_create("label", stream->label));
					if(stream->msid && stream->mstid) {
						char msid[150];
						g_snprintf(msid, sizeof(msid), "%s %s", stream->msid, stream->mstid);
						janus_config_add(config, m, janus_config_item_create("msid", msid));
					}
					if(stream->port[0] > 0) {
						g_snprintf(value, BUFSIZ, "%d", stream->port[0]);
						janus_config_add(config, m, janus_config_item_create("port", value));
					}
					if(stream->rtcp_port > 0) {
						g_snprintf(value, BUFSIZ, "%d", stream->rtcp_port);
						janus_config_add(config, m, janus_config_item_create("rtcpport", value));
					}
					if(stream->codecs.pt >= 0) {
						g_snprintf(value, BUFSIZ, "%d", stream->codecs.pt);
						janus_config_add(config, m, janus_config_item_create("pt", value));
					}
					if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE || stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE) {
						if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE)
							janus_config_add(config, m, janus_config_item_create("codec", janus_audiocodec_name(stream->codecs.audio_codec)));
						else if(stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE)
							janus_config_add(config, m, janus_config_item_create("codec", janus_videocodec_name(stream->codecs.video_codec)));
						if(stream->codecs.fmtp)
							janus_config_add(config, m, janus_config_item_create("fmtp", stream->codecs.fmtp));
						if(stream->skew)
							janus_config_add(config, m, janus_config_item_create("skew", "true"));
					}
					if(stream->simulcast) {
						janus_config_add(config, m, janus_config_item_create("videosimulcast", "true"));
						if(stream->port[1]) {
							g_snprintf(value, BUFSIZ, "%d", stream->port[1]);
							janus_config_add(config, m, janus_config_item_create("port2", value));
						}
						if(stream->port[2]) {
							g_snprintf(value, BUFSIZ, "%d", stream->port[2]);
							janus_config_add(config, m, janus_config_item_create("port3", value));
						}
					}
					if(stream->svc)
						janus_config_add(config, m, janus_config_item_create("videosvc", "true"));
					if(stream->skew)
						janus_config_add(config, m, janus_config_item_create("skew", "true"));
					if(stream->mcast_str)
						janus_config_add(config, m, janus_config_item_create("mcast", stream->mcast_str));
					if(stream->iface_str)
						janus_config_add(config, m, janus_config_item_create("iface", stream->iface_str));
					if(stream->type == JANUS_STREAMING_MEDIA_DATA)
						janus_config_add(config, m, janus_config_item_create("datatype", stream->textdata ? "text" : "binary"));
					if(stream->buffermsg)
						janus_config_add(config, m, janus_config_item_create("databuffermsg", "true"));
					temp = temp->next;
				}
			} else if(mp->streaming_source == janus_streaming_source_file) {
				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", "true"));
			} else if(mp->streaming_source == janus_streaming_source_rtp && ((janus_streaming_rtp_source *)mp->source)->rtsp) {
				janus_streaming_rtp_source *source = mp->source;
#ifdef HAVE_LIBCURL
				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));
				if(source->rtsp_quirk)
					janus_config_add(config, c, janus_config_item_create("rtsp_quirk", "true"));
#endif
				if(source->bufferkf_ms > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu16, source->bufferkf_ms);
					janus_config_add(config, c, janus_config_item_create("bufferkf_ms", value));
				}
				if(source->bufferkf_bytes > 0) {
					g_snprintf(value, BUFSIZ, "%"SCNu32, source->bufferkf_bytes);
					janus_config_add(config, c, janus_config_item_create("bufferkf_bytes", value));
				}
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					/* FIXME Should we support RTSP streams with multiple media? */
					if(stream->type == JANUS_STREAMING_MEDIA_AUDIO) {
						janus_config_add(config, c, janus_config_item_create("audio", "true"));
						if(stream->codecs.audio_codec != JANUS_AUDIOCODEC_NONE)
							janus_config_add(config, c, janus_config_item_create("audiocodec", janus_audiocodec_name(stream->codecs.audio_codec)));
						if(stream->codecs.fmtp)
							janus_config_add(config, c, janus_config_item_create("audiofmtp", stream->codecs.fmtp));
					} else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
						janus_config_add(config, c, janus_config_item_create("video", "true"));
						if(stream->codecs.video_codec != JANUS_VIDEOCODEC_NONE)
							janus_config_add(config, c, janus_config_item_create("videocodec", janus_videocodec_name(stream->codecs.video_codec)));
						if(stream->codecs.fmtp)
							janus_config_add(config, c, janus_config_item_create("videofmtp", stream->codecs.fmtp));
					}
					temp = temp->next;
				}
				json_t *iface = json_object_get(root, "rtspiface");
				if(iface)
					janus_config_add(config, c, janus_config_item_create("rtspiface", json_string_value(iface)));
				if(mp->helper_threads > 0) {
					g_snprintf(value, BUFSIZ, "%d", mp->helper_threads);
					janus_config_add(config, c, janus_config_item_create("threads", value));
				}
			}
			/* 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", string_ids ? json_string(mp->id_str) : 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", string_ids ? json_string(mp->id_str) : json_integer(mp->id));
			gateway->notify_event(&janus_streaming_plugin, session ? session->handle : NULL, info);
		}

		/* Also notify viewers */
		if(send_edited_event) {
			json_t *info = json_object();
			json_object_set_new(info, "event", json_string("edited"));
			json_object_set_new(info, "id", string_ids ? json_string(mp->id_str) : json_integer(mp->id));
			if(mp->metadata)
				json_object_set_new(info, "metadata", json_string(mp->metadata));
			janus_streaming_notify_subscribers(mp, info);
			json_decref(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, "kick_all")) {
		/* Note the kick_all request works with all mountpoint types except for on-demand streaming,
		 * because each on-demand viewer has their own thread and their own playback context. */
		if(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstr_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 = 0;
		char id_num[30], *id_value_str = NULL;
		if(!string_ids) {
			id_value = json_integer_value(id);
			g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
			id_value_str = id_num;
		} else {
			id_value_str = (char *)json_string_value(id);
		}
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
			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 kick all viewers from mountpoint/stream %s\n", id_value_str);
		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("kicked_all"));
		json_object_set_new(event, "result", result);
		while(viewer) {
			janus_streaming_session *s = (janus_streaming_session *)viewer->data;
			if(s == NULL) {
				mp->viewers = g_list_remove_all(mp->viewers, s);
				viewer = g_list_first(mp->viewers);
				continue;
			}
			janus_mutex_lock(&s->mutex);
			if(s->mountpoint != mp) {
				mp->viewers = g_list_remove_all(mp->viewers, s);
				viewer = g_list_first(mp->viewers);
				janus_mutex_unlock(&s->mutex);
				continue;
			}
			g_atomic_int_set(&s->stopping, 1);
			g_atomic_int_set(&s->started, 0);
			g_atomic_int_set(&s->paused, 0);
			s->mountpoint = NULL;
			janus_mutex_unlock(&s->mutex);
			/* 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);
			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);
			janus_refcount_decrease(&s->ref);
			janus_refcount_decrease(&mp->ref);
		}
		json_decref(event);
		janus_mutex_unlock(&mp->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("kicked_all"));
			json_object_set_new(info, "id", string_ids ? json_string(id_value_str) : 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("kicked_all"));
		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;
		if(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstr_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 = 0;
		char id_num[30], *id_value_str = NULL;
		if(!string_ids) {
			id_value = json_integer_value(id);
			g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
			id_value_str = id_num;
		} else {
			id_value_str = (char *)json_string_value(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;
		}
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
			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 %s\n", id_value_str);
		/* Remove mountpoint from the hashtable: this will get it destroyed eventually */
		g_hash_table_remove(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&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) {
				mp->viewers = g_list_remove_all(mp->viewers, s);
				viewer = g_list_first(mp->viewers);
				continue;
			}
			janus_mutex_lock(&s->mutex);
			if(s->mountpoint != mp) {
				mp->viewers = g_list_remove_all(mp->viewers, s);
				viewer = g_list_first(mp->viewers);
				janus_mutex_unlock(&s->mutex);
				continue;
			}
			g_atomic_int_set(&s->stopping, 1);
			g_atomic_int_set(&s->started, 0);
			g_atomic_int_set(&s->paused, 0);
			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);
			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);
			janus_mutex_unlock(&s->mutex);
			janus_refcount_decrease(&s->ref);
			janus_refcount_decrease(&mp->ref);
		}
		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 %s (%s) permanently in config file\n", mp->id_str, 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", string_ids ? json_string(id_value_str) : 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", string_ids ? json_string(id_value_str) : 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;
		}
		if(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstr_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 = 0;
		char id_num[30], *id_value_str = NULL;
		if(!string_ids) {
			id_value = json_integer_value(id);
			g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
			id_value_str = id_num;
		} else {
			id_value_str = (char *)json_string_value(id);
		}
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
			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 *media = json_object_get(root, "media");
			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_mutex_lock(&source->rec_mutex);
			if(media) {
				/* Iterate on all media to start */
				if(json_array_size(media) > 0) {
					size_t i = 0;
					for(i=0; i<json_array_size(media); i++) {
						json_t *m = json_array_get(media, i);
						if(!json_is_object(m))
							continue;
						const char *mid = json_string_value(json_object_get(m, "mid"));
						const char *filename = json_string_value(json_object_get(m, "filename"));
						if(mid == NULL || filename == NULL) {
							JANUS_LOG(LOG_WARN, "No mid/filename, can't start recording for this stream...\n");
							continue;
						}
						GList *temp = source->media;
						while(temp) {
							janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
							if(strcasecmp(stream->mid, mid)) {
								temp = temp->next;
								continue;
							}
							if(stream->rc) {
								janus_mutex_unlock(&source->rec_mutex);
								janus_refcount_decrease(&mp->ref);
								janus_mutex_unlock(&mountpoints_mutex);
								JANUS_LOG(LOG_ERR, "Recording 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;
							}
							const char *codec = NULL;
							if(stream->type == JANUS_STREAMING_MEDIA_AUDIO) {
								codec = janus_audiocodec_name(stream->codecs.audio_codec);
							} else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
								codec = janus_videocodec_name(stream->codecs.video_codec);
							} else {
								codec = "text";
							}
							janus_recorder *rc = janus_recorder_create(NULL, codec, (char *)filename);
							if(rc == NULL) {
								JANUS_LOG(LOG_ERR, "[%s] Error starting recorder for %s\n",
									mp->name, janus_streaming_media_str(stream->type));
								janus_mutex_unlock(&source->rec_mutex);
								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 %s", janus_streaming_media_str(stream->type));
								goto prepare_response;
							}
							/* If media is encrypted, mark it in the recording */
							if(source->e2ee)
								janus_recorder_encrypted(rc);
							stream->rc = rc;
							JANUS_LOG(LOG_INFO, "[%s] %s recording started (%s)\n",
								mp->name, janus_streaming_media_str(stream->type), stream->mid);
							break;
						}
					}
				}
				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;
			}
			/* If we're here, we're using the old method to record */
			if(!audio && !video && !data) {
				janus_mutex_unlock(&source->rec_mutex);
				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;
			}
			GList *temp = source->media;
			while(temp) {
				janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
				if((stream->type == JANUS_STREAMING_MEDIA_AUDIO && !audio) ||
						(stream->type == JANUS_STREAMING_MEDIA_VIDEO && !video) ||
						(stream->type == JANUS_STREAMING_MEDIA_DATA && !data)) {
					temp = temp->next;
					continue;
				}
				if(stream->rc) {
					janus_mutex_unlock(&source->rec_mutex);
					janus_refcount_decrease(&mp->ref);
					janus_mutex_unlock(&mountpoints_mutex);
					JANUS_LOG(LOG_ERR, "Recording already started for this %s stream\n", janus_streaming_media_str(stream->type));
					error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST;
					g_snprintf(error_cause, 512, "Recording started for this stream");
					goto prepare_response;
				}
				const char *codec = NULL, *file = NULL;
				if(stream->type == JANUS_STREAMING_MEDIA_AUDIO) {
					codec = janus_audiocodec_name(stream->codecs.audio_codec);
					file = json_string_value(audio);
				} else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
					codec = janus_videocodec_name(stream->codecs.video_codec);
					file = json_string_value(video);
				} else if(stream->type == JANUS_STREAMING_MEDIA_DATA) {
					codec = "text";
					file = json_string_value(data);
				}
				janus_recorder *rc = janus_recorder_create(NULL, codec, (char *)file);
				if(rc == NULL) {
					JANUS_LOG(LOG_ERR, "[%s] Error starting recorder for %s\n", mp->name, janus_streaming_media_str(stream->type));
					janus_mutex_unlock(&source->rec_mutex);
					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");
					goto prepare_response;
				}
				/* If media is encrypted, mark it in the recording */
				if(source->e2ee)
					janus_recorder_encrypted(rc);
				JANUS_LOG(LOG_INFO, "[%s] Recording for %s started\n", mp->name, janus_streaming_media_str(stream->type));
				stream->rc = rc;
				temp = temp->next;
			}
			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(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_refcount_decrease(&mp->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				goto prepare_response;
			}
			json_t *media = json_object_get(root, "media");
			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_mutex_lock(&source->rec_mutex);
			if(media) {
				/* Iterate on all media to stop */
				if(json_array_size(media) > 0) {
					size_t i = 0;
					for(i=0; i<json_array_size(media); i++) {
						json_t *m = json_array_get(media, i);
						if(!json_is_object(m))
							continue;
						const char *mid = json_string_value(json_object_get(m, "mid"));
						if(mid == NULL)
							continue;
						GList *temp = source->media;
						while(temp) {
							janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
							if(strcasecmp(stream->mid, mid) || stream->rc == NULL) {
								temp = temp->next;
								continue;
							}
							janus_recorder_close(stream->rc);
							JANUS_LOG(LOG_INFO, "[%s] Closed %s recording %s (%s)\n", mp->name,
								janus_streaming_media_str(stream->type), stream->rc->filename, stream->mid);
							janus_recorder *tmp = stream->rc;
							stream->rc = NULL;
							janus_recorder_destroy(tmp);
							break;
						}
					}
				}
				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;
			}
			/* If we're here, we're using the old method to stop recording */
			if(!audio && !video && !data) {
				janus_mutex_unlock(&source->rec_mutex);
				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;
			}
			GList *temp = source->media;
			while(temp) {
				janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
				if((stream->type == JANUS_STREAMING_MEDIA_AUDIO && !json_is_true(audio)) ||
						(stream->type == JANUS_STREAMING_MEDIA_VIDEO && !json_is_true(video)) ||
						(stream->type == JANUS_STREAMING_MEDIA_DATA && !json_is_true(data)) || stream->rc == NULL) {
					temp = temp->next;
					continue;
				}
				/* Close the recording */
				janus_recorder_close(stream->rc);
				JANUS_LOG(LOG_INFO, "[%s] Closed %s recording %s (%s)\n", mp->name,
					janus_streaming_media_str(stream->type), stream->rc->filename, stream->mid);
				janus_recorder *tmp = stream->rc;
				stream->rc = NULL;
				janus_recorder_destroy(tmp);
				temp = temp->next;
			}
			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 */
		if(!string_ids) {
			JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
				error_code, error_cause, TRUE,
				JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
		} else {
			JANUS_VALIDATE_JSON_OBJECT(root, idstr_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 = 0;
		char id_num[30], *id_value_str = NULL;
		if(!string_ids) {
			id_value = json_integer_value(id);
			g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
			id_value_str = id_num;
		} else {
			id_value_str = (char *)json_string_value(id);
		}
		janus_mutex_lock(&mountpoints_mutex);
		janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
			string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
		if(mp == NULL) {
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
			error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
			g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
			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_VALIDATE_JSON_OBJECT(root, disable_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;
			}
			mp->enabled = FALSE;
			gboolean stop_recording = TRUE;
			json_t *stop_rec = json_object_get(root, "stop_recording");
			if (stop_rec) {
				stop_recording = json_is_true(stop_rec);
			}
			JANUS_LOG(LOG_INFO, "[%s] Stream disabled (stop_recording=%s)\n", mp->name, stop_recording ? "true" : "false");
			/* Any recording to close? */
			if(mp->streaming_source == janus_streaming_source_rtp && stop_recording) {
				janus_streaming_rtp_source *source = mp->source;
				janus_mutex_lock(&source->rec_mutex);
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					/* Close the recording */
					janus_recorder_close(stream->rc);
					JANUS_LOG(LOG_INFO, "[%s] Closed %s recording %s (%s)\n", mp->name,
						janus_streaming_media_str(stream->type), stream->rc->filename, stream->mid);
					janus_recorder *tmp = stream->rc;
					stream->rc = NULL;
					janus_recorder_destroy(tmp);
					temp = temp->next;
				}
				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);
	/* If this is related to a live RTP mountpoint, any keyframe we can shoot already? */
	janus_streaming_mountpoint *mountpoint = session->mountpoint;
	if (!mountpoint) {
		janus_refcount_decrease(&session->ref);
		JANUS_LOG(LOG_ERR, "No mountpoint associated with this session...\n");
		return;
	}
	if(mountpoint->streaming_source == janus_streaming_source_rtp) {
		janus_streaming_rtp_source *source = mountpoint->source;
		GList *temp = source->media;
		while(temp) {
			janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
			if(stream->keyframe.enabled) {
				JANUS_LOG(LOG_HUGE, "Any keyframe to send? (%s)\n", stream->mid);
				janus_mutex_lock(&stream->keyframe.mutex);
				if(stream->keyframe.latest_keyframe != NULL) {
					JANUS_LOG(LOG_HUGE, "Yep! %d packets\n", g_list_length(stream->keyframe.latest_keyframe));
					GList *packets = g_list_reverse(g_list_copy(stream->keyframe.latest_keyframe)), *temp = packets;
					while(temp) {
						janus_streaming_relay_rtp_packet(session, temp->data);
						temp = temp->next;
					}
					g_list_free(packets);
				}
				janus_mutex_unlock(&stream->keyframe.mutex);
			}
			/* If this mountpoint has RTCP support, send a PLI */
			if(stream->type == JANUS_STREAMING_MEDIA_VIDEO)
				janus_streaming_rtcp_pli_send(stream);
			temp = temp->next;
		}
	}
	g_atomic_int_set(&session->started, 1);
	/* 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);
	/* Also notify event handlers */
	if(notify_events && gateway->events_is_enabled()) {
		json_t *info = json_object();
		json_object_set_new(info, "status", json_string("started"));
		if(session->mountpoint != NULL)
			json_object_set_new(info, "id", string_ids ?
				json_string(session->mountpoint->id_str) :json_integer(session->mountpoint->id));
		gateway->notify_event(&janus_streaming_plugin, session->handle, info);
	}
	janus_refcount_decrease(&session->ref);
}

void janus_streaming_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet) {
	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, janus_plugin_rtcp *packet) {
	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) || g_atomic_int_get(&session->stopping) ||
			!g_atomic_int_get(&session->started) || g_atomic_int_get(&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;
	/* Check which stream this feedback refers to */
	janus_streaming_rtp_source_stream *stream = g_hash_table_lookup(source->media_byid, GINT_TO_POINTER(packet->mindex));
	if(stream == NULL)
		return;
	gboolean video = packet->video;
	char *buf = packet->buffer;
	uint16_t len = packet->length;
	if(!video && (stream->rtcp_fd > -1) && (stream->rtcp_addr.ss_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 && (stream->rtcp_fd > -1) && (stream->rtcp_addr.ss_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(stream);
		}
		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_data_ready(janus_plugin_session *handle) {
	if(handle == NULL || g_atomic_int_get(&handle->stopped) ||
			g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
		return;
	/* Data channels are writable: we shouldn't send any datachannel message before this happens */
	janus_streaming_session *session = (janus_streaming_session *)handle->plugin_handle;
	if(!session || g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&session->hangingup))
		return;
	janus_refcount_increase(&session->ref);
	if(g_atomic_int_compare_and_exchange(&session->dataready, 0, 1)) {
		JANUS_LOG(LOG_INFO, "[%s-%p] Data channel available\n", JANUS_STREAMING_PACKAGE, handle);
		/* Try to send a buffered datachannel message when datachannel is ready */
		GList *temp = session->streams;
		janus_streaming_session_stream *s;
		janus_streaming_rtp_source_stream *stream;
		while(temp) {
			s = (janus_streaming_session_stream *)temp->data;
			stream = s->stream;
			if(stream->buffermsg) {
				janus_refcount_increase(&stream->ref);
				JANUS_LOG(LOG_VERB, "[%s-%p] Trying to send the most recent message (%s)\n", JANUS_STREAMING_PACKAGE, handle, stream->mid);
				janus_mutex_lock(&stream->buffermsg_mutex);
				if(stream->last_msg != NULL) {
					JANUS_LOG(LOG_HUGE, "Buffered datachannel message found!\n");
					janus_streaming_relay_rtp_packet(session, stream->last_msg);
				}
				janus_mutex_unlock(&stream->buffermsg_mutex);
				janus_refcount_decrease(&stream->ref);
			}
			temp = temp->next;
		}
	}
	janus_refcount_decrease(&session->ref);
}

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;
	g_atomic_int_set(&session->dataready, 0);
	g_atomic_int_set(&session->stopping, 1);
	g_atomic_int_set(&session->started, 0);
	g_atomic_int_set(&session->paused, 0);
	session->e2ee = FALSE;
	janus_mutex_lock(&session->mutex);
	janus_streaming_mountpoint *mp = session->mountpoint;
	session->mountpoint = NULL;
	janus_mutex_unlock(&session->mutex);
	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;
				}
			}
		}
		/* Get rid of streams and streams_byid while holding the mountpoint mutex */
		g_list_free_full(session->streams, (GDestroyNotify)(janus_streaming_session_stream_free));
		session->streams = NULL;
		if(session->streams_byid != NULL)
			g_hash_table_unref(session->streams_byid);
		session->streams_byid = NULL;
		janus_mutex_unlock(&mp->mutex);
	} else {
		/* Get rid of streams and streams_byid */
		g_list_free_full(session->streams, (GDestroyNotify)(janus_streaming_session_stream_free));
		session->streams = NULL;
		if(session->streams_byid != NULL)
			g_hash_table_unref(session->streams_byid);
		session->streams_byid = 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;
		}
		/* Handle request */
		error_code = 0;
		root = NULL;
		if(msg->message == NULL) {
			janus_mutex_unlock(&sessions_mutex);
			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) {
			janus_mutex_unlock(&sessions_mutex);
			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 = json_string_value(json_object_get(msg->jsep, "type"));
		const char *jsep_sdp = (char *)json_string_value(json_object_get(msg->jsep, "sdp"));
		char *sdp = NULL;
		gboolean do_restart = FALSE;
		/* All these requests can only be handled asynchronously */
		if(!strcasecmp(request_text, "watch") && jsep_sdp == NULL) {
			/* New subscriber, plugin will generate an offer */
			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) {
				janus_mutex_unlock(&sessions_mutex);
				goto error;
			}
			if(!string_ids) {
				JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			} else {
				JANUS_VALIDATE_JSON_OBJECT(root, idstr_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			}
			if(error_code != 0) {
				janus_mutex_unlock(&sessions_mutex);
				goto error;
			}
			json_t *id = json_object_get(root, "id");
			guint64 id_value = 0;
			char id_num[30], *id_value_str = NULL;
			if(!string_ids) {
				id_value = json_integer_value(id);
				g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
				id_value_str = id_num;
			} else {
				id_value_str = (char *)json_string_value(id);
			}
			/* The proper way is listing the mids of the streams we want to
			 * receive: a missing or empty list will subscribe to them all */
			json_t *mids = json_object_get(root, "media");
			if(mids != NULL && json_array_size(mids) > 0) {
				size_t i = 0;
				for(i=0; i<json_array_size(mids); i++) {
					json_t *s = json_array_get(mids, i);
					if(!json_is_string(s)) {
						janus_mutex_unlock(&sessions_mutex);
						error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
						g_snprintf(error_cause, 512, "The media array must only contain strings (mid values)");
						goto error;
					}
				}
			}
			/* The offer_audio/video/data attribute are deprecated,
			 * but we keep them there for backwards compatibility */
			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");
			/* There may be an ICE restart request involved */
			json_t *restart = json_object_get(root, "restart");
			do_restart = restart ? json_is_true(restart) : FALSE;
			/* Find the mountpoint and go on */
			janus_mutex_lock(&mountpoints_mutex);
			janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
				string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
			if(mp == NULL) {
				janus_mutex_unlock(&mountpoints_mutex);
				janus_mutex_unlock(&sessions_mutex);
				JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
				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);
				janus_mutex_unlock(&sessions_mutex);
				goto error;
			}
			janus_mutex_lock(&mp->mutex);
			janus_mutex_lock(&session->mutex);
			janus_mutex_unlock(&mountpoints_mutex);
			/* Check if this is a new viewer, or if an update is taking place (i.e., ICE restart) */
			gboolean audio = TRUE, video = TRUE, data = TRUE;
			if(do_restart) {
				if(session->mountpoint == NULL) {
					JANUS_LOG(LOG_ERR, "Can't perform ICE restart: no mountpoint set\n");
					error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
					g_snprintf(error_cause, 512, "Can't perform ICE restart: no mountpoint set");
					janus_mutex_unlock(&session->mutex);
					janus_mutex_unlock(&mp->mutex);
					janus_mutex_unlock(&sessions_mutex);
					janus_refcount_decrease(&mp->ref);
					goto error;
				}
				if(session->mountpoint != mp) {
					/* Already watching something else */
					JANUS_LOG(LOG_ERR, "Already watching mountpoint %s\n", session->mountpoint->id_str);
					error_code = JANUS_STREAMING_ERROR_INVALID_STATE;
					g_snprintf(error_cause, 512, "Already watching mountpoint %s", session->mountpoint->id_str);
					janus_mutex_unlock(&session->mutex);
					janus_mutex_unlock(&mp->mutex);
					janus_mutex_unlock(&sessions_mutex);
					janus_refcount_decrease(&mp->ref);
					goto error;
				}
				/* User asked for an ICE restart: provide a new offer */
				if(!g_atomic_int_compare_and_exchange(&session->renegotiating, 0, 1)) {
					/* Already triggered a renegotiation, and still waiting for an answer */
					janus_mutex_unlock(&session->mutex);
					janus_mutex_unlock(&mp->mutex);
					janus_mutex_unlock(&sessions_mutex);
					JANUS_LOG(LOG_ERR, "Already renegotiating mountpoint %s\n", session->mountpoint->id_str);
					error_code = JANUS_STREAMING_ERROR_INVALID_STATE;
					g_snprintf(error_cause, 512, "Already renegotiating mountpoint %s", session->mountpoint->id_str);
					janus_refcount_decrease(&mp->ref);
					goto error;
				}
				janus_refcount_decrease(&mp->ref);
				JANUS_LOG(LOG_VERB, "Request to perform an ICE restart on mountpoint/stream %s subscription\n", id_value_str);
				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_LOG(LOG_ERR, "Already watching mountpoint %s\n", session->mountpoint->id_str);
					error_code = JANUS_STREAMING_ERROR_INVALID_STATE;
					g_snprintf(error_cause, 512, "Already watching mountpoint %s", session->mountpoint->id_str);
					janus_mutex_unlock(&session->mutex);
					janus_mutex_unlock(&mp->mutex);
					janus_mutex_unlock(&sessions_mutex);
					janus_refcount_decrease(&mp->ref);
					goto error;
				} else {
					/* Make sure it's not an API error */
					if(!g_atomic_int_get(&session->started)) {
						/* Can't be a renegotiation, PeerConnection isn't up yet */
						JANUS_LOG(LOG_ERR, "Already watching mountpoint %s\n", session->mountpoint->id_str);
						error_code = JANUS_STREAMING_ERROR_INVALID_STATE;
						g_snprintf(error_cause, 512, "Already watching mountpoint %s", session->mountpoint->id_str);
						janus_refcount_decrease(&mp->ref);
						janus_mutex_unlock(&session->mutex);
						janus_mutex_unlock(&mp->mutex);
						janus_mutex_unlock(&sessions_mutex);
						goto error;
					}
					if(!g_atomic_int_compare_and_exchange(&session->renegotiating, 0, 1)) {
						/* Already triggered a renegotiation, and still waiting for an answer */
						JANUS_LOG(LOG_ERR, "Already renegotiating mountpoint %s\n", session->mountpoint->id_str);
						error_code = JANUS_STREAMING_ERROR_INVALID_STATE;
						g_snprintf(error_cause, 512, "Already renegotiating mountpoint %s", session->mountpoint->id_str);
						janus_refcount_decrease(&mp->ref);
						janus_mutex_unlock(&session->mutex);
						janus_mutex_unlock(&mp->mutex);
						janus_mutex_unlock(&sessions_mutex);
						goto error;
					}
					/* Simple renegotiation, remove the extra unneeded reference */
					janus_refcount_decrease(&mp->ref);
					JANUS_LOG(LOG_VERB, "Request to update mountpoint/stream %s subscription (no restart)\n", id_value_str);
					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 %s\n", id_value_str);
			if(g_list_find(mp->viewers, session) != NULL) {
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&mp->mutex);
				janus_mutex_unlock(&sessions_mutex);
				janus_refcount_decrease(&mp->ref);
				JANUS_LOG(LOG_ERR, "Already watching a stream (found %p in %s's viewers)...\n", session, id_value_str);
				error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "Already watching a stream");
				goto error;
			}
			g_atomic_int_set(&session->stopping, 0);
			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: the new way of subscribing is
			 * specifying the streams we're interested in by mid */
			gboolean legacy = FALSE;
			if(mids == NULL || json_array_size(mids) == 0) {
				/* We'll treat this as a legacy request, even though
				 * it may simply be a "subscribe to all streams" */
				legacy = TRUE;
			}
			/* These are deprecated attributes, that we handle for backwards compatibility */
			audio = offer_audio ? json_is_true(offer_audio) : TRUE;	/* True by default */
			if(!mp->audio)
				audio = FALSE;	/* ... unless the mountpoint isn't sending any audio */
			video = offer_video ? json_is_true(offer_video) : TRUE;	/* True by default */
			if(!mp->video)
				video = FALSE;	/* ... unless the mountpoint isn't sending any video */
			data = offer_data ? json_is_true(offer_data) : TRUE;	/* True by default */
			if(!mp->data)
				data = FALSE;	/* ... unless the mountpoint isn't sending any data */
			if((!mp->audio || !audio) && (!mp->video || !video) && (!mp->data || !data)) {
				session->mountpoint = NULL;
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&mp->mutex);
				janus_mutex_unlock(&sessions_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_source == janus_streaming_source_file) {
				/* Create a session stream */
				janus_streaming_session_stream *s = g_malloc0(sizeof(janus_streaming_session_stream));
				s->mindex = -1;
				s->send = TRUE;
				s->pt = -1;
				janus_rtp_switching_context_reset(&s->context);
				s->min_delay = -1;
				s->max_delay = -1;
				session->streams = g_list_append(session->streams, s);
				if(session->streams_byid == NULL)
					session->streams_byid = g_hash_table_new(NULL, NULL);
				g_hash_table_insert(session->streams_byid, GINT_TO_POINTER(s->mindex), s);
			}
			if(mp->streaming_type == janus_streaming_type_on_demand) {
				/* Spawn a thread */
				GError *error = NULL;
				char tname[16];
				g_snprintf(tname, sizeof(tname), "mp %s", mp->id_str);
				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(&session->mutex);
					janus_refcount_decrease(&session->ref);	/* This is for the failed thread */
					janus_mutex_unlock(&mp->mutex);
					janus_mutex_unlock(&sessions_mutex);
					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 : "??");
					g_error_free(error);
					goto error;
				}
			} else if(mp->streaming_source == janus_streaming_source_rtp) {
				/* Create a session stream for each source stream we're subscribing to */
				janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)mp->source;
				janus_streaming_session_stream *s = NULL;
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					/* If we're using the legacy API, make sure we honor the deprecated attributes */
					if(legacy && ((stream->type == JANUS_STREAMING_MEDIA_AUDIO && !audio) ||
							(stream->type == JANUS_STREAMING_MEDIA_VIDEO && !video) ||
							(stream->type == JANUS_STREAMING_MEDIA_DATA && !data))) {
						/* FIXME Don't subscribe to this stream */
						temp = temp->next;
						continue;
					}
					/* Check if this stream is among the mids the user subscribed to */
					if(mids != NULL && json_array_size(mids) > 0) {
						gboolean found = FALSE;
						size_t i = 0;
						for(i=0; i<json_array_size(mids); i++) {
							json_t *s = json_array_get(mids, i);
							const char *mid = json_string_value(s);
							if(mid && stream->mid && !strcasecmp(stream->mid, mid)) {
								found = TRUE;
								break;
							}
						}
						if(!found) {
							/* Not in the mids list, don't subscribe to this stream */
							temp = temp->next;
							continue;
						}
					}
					/* Create a new session stream and add a reference to the source stream */
					s = g_malloc0(sizeof(janus_streaming_session_stream));
					s->send = TRUE;
					s->pt = stream->codecs.pt;
					janus_rtp_switching_context_reset(&s->context);
					s->min_delay = -1;
					s->max_delay = -1;
					if(stream && stream->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) {
							g_free(s);
							session->mountpoint = NULL;
							janus_mutex_unlock(&session->mutex);
							janus_mutex_unlock(&mp->mutex);
							janus_mutex_unlock(&sessions_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(&s->context);
						janus_rtp_simulcasting_context_reset(&s->sim_context);
						s->sim_context.substream_target = 2;
						s->sim_context.templayer_target = 2;
						janus_vp8_simulcast_context_reset(&s->vp8_context);
						/* Unless the request contains a target for either layer */
						json_t *substream = json_object_get(root, "substream");
						if(substream) {
							s->sim_context.substream_target = json_integer_value(substream);
							JANUS_LOG(LOG_VERB, "Setting video substream to let through (simulcast): %d (was %d)\n",
								s->sim_context.substream_target, s->sim_context.substream);
						}
						json_t *temporal = json_object_get(root, "temporal");
						if(temporal) {
							s->sim_context.templayer_target = json_integer_value(temporal);
							JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n",
								s->sim_context.templayer_target, s->sim_context.templayer);
						}
						/* Check if we need a custom fallback timer for the substream */
						json_t *fallback = json_object_get(root, "fallback");
						if(fallback) {
							JANUS_LOG(LOG_VERB, "Setting fallback timer (simulcast): %lld (was %"SCNu32")\n",
								json_integer_value(fallback) ? json_integer_value(fallback) : 250000,
								s->sim_context.drop_trigger ? s->sim_context.drop_trigger : 250000);
							s->sim_context.drop_trigger = json_integer_value(fallback);
						}
					} else if(stream && stream->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) {
							g_free(s);
							session->mountpoint = NULL;
							janus_mutex_unlock(&session->mutex);
							janus_mutex_unlock(&mp->mutex);
							janus_mutex_unlock(&sessions_mutex);
							janus_refcount_decrease(&mp->ref);
							goto error;
						}
						/* In case this mountpoint is doing VP9-SVC, let's aim high by default */
						s->spatial_layer = -1;
						s->target_spatial_layer = 2;	/* FIXME Chrome sends 0, 1 and 2 (if using EnabledByFlag_3SL3TL) */
						s->temporal_layer = -1;
						s->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) {
							s->target_spatial_layer = json_integer_value(spatial);
							JANUS_LOG(LOG_VERB, "Setting video spatial layer to let through (SVC): %d (was %d)\n",
								s->target_spatial_layer, s->spatial_layer);
						}
						json_t *temporal = json_object_get(root, "temporal_layer");
						if(temporal) {
							s->target_temporal_layer = json_integer_value(temporal);
							JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (SVC): %d (was %d)\n",
								s->target_temporal_layer, s->temporal_layer);
						}
					}
					s->mindex = g_list_length(session->streams);
					s->stream = stream;
					janus_refcount_increase(&stream->ref);
					session->streams = g_list_append(session->streams, s);
					if(session->streams_byid == NULL)
						session->streams_byid = g_hash_table_new(NULL, NULL);
					g_hash_table_insert(session->streams_byid, GINT_TO_POINTER(stream->mindex), s);
					temp = temp->next;
				}
				/* If this mountpoint is broadcasting end-to-end encrypted media,
				 * add the info to the JSEP offer we'll be sending them */
				session->e2ee = source->e2ee;
				/* Also check if we have to offer the playout-delay extension */
				session->playoutdelay_ext = source->playoutdelay_ext;
				/* Also check if we have to offer the abs-capture-time extension */
				session->abscapturetime_src_ext_id = source->abscapturetime_src_ext_id;
			}
			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 s_name[100];
			g_snprintf(s_name, sizeof(s_name), "Mountpoint %s", mp->id_str);
			janus_sdp *offer = janus_sdp_generate_offer(s_name, "0.0.0.0",
				JANUS_SDP_OA_DONE);
			offer->o_version = session->sdp_version;
			if(mp->streaming_source == janus_streaming_source_file) {
				janus_streaming_file_source *source = mp->source;
				/* Add audio line */
				janus_sdp_generate_offer_mline(offer,
					JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO,
					JANUS_SDP_OA_PT, source->codecs.pt,
					JANUS_SDP_OA_CODEC, janus_audiocodec_name(source->codecs.audio_codec),
					JANUS_SDP_OA_FMTP, source->codecs.fmtp,
					JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY,
					JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, janus_rtp_extension_id(JANUS_RTP_EXTMAP_MID),
					JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_ABS_SEND_TIME, janus_rtp_extension_id(JANUS_RTP_EXTMAP_ABS_SEND_TIME),
					JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME,
						(session->abscapturetime_src_ext_id > 0 ? janus_rtp_extension_id(JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME) : 0),
					JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_PLAYOUT_DELAY,
						(session->playoutdelay_ext ? janus_rtp_extension_id(JANUS_RTP_EXTMAP_PLAYOUT_DELAY) : 0),
					JANUS_SDP_OA_DONE);
			} else {
				/* Iterate on all media streams */
				GList *temp = session->streams;
				while(temp) {
					janus_streaming_session_stream *s = (janus_streaming_session_stream *)temp->data;
					janus_streaming_rtp_source_stream *stream = s->stream;
					int pt = s->pt > 0 ? s->pt : stream->codecs.pt;
					gboolean add_msid = (stream->msid && stream->mstid);
					if(stream->type == JANUS_STREAMING_MEDIA_AUDIO && audio) {
						/* Add audio line */
						janus_sdp_generate_offer_mline(offer,
							JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO,
							JANUS_SDP_OA_MID, stream->mid,
							JANUS_SDP_OA_MSID, add_msid ? stream->msid : NULL, add_msid ? stream->mstid : NULL,
							JANUS_SDP_OA_PT, pt,
							JANUS_SDP_OA_CODEC, janus_audiocodec_name(stream->codecs.audio_codec),
							JANUS_SDP_OA_FMTP, stream->codecs.fmtp,
							JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY,
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, janus_rtp_extension_id(JANUS_RTP_EXTMAP_MID),
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_ABS_SEND_TIME, janus_rtp_extension_id(JANUS_RTP_EXTMAP_ABS_SEND_TIME),
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME,
								(session->abscapturetime_src_ext_id > 0 ? janus_rtp_extension_id(JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME) : 0),
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_PLAYOUT_DELAY,
								(session->playoutdelay_ext ? janus_rtp_extension_id(JANUS_RTP_EXTMAP_PLAYOUT_DELAY) : 0),
							JANUS_SDP_OA_DONE);
					} else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO && video) {
						/* Add video line */
						janus_sdp_generate_offer_mline(offer,
							JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO,
							JANUS_SDP_OA_MID, stream->mid,
							JANUS_SDP_OA_MSID, add_msid ? stream->msid : NULL, add_msid ? stream->mstid : NULL,
							JANUS_SDP_OA_PT, pt,
							JANUS_SDP_OA_CODEC, janus_videocodec_name(stream->codecs.video_codec),
							JANUS_SDP_OA_FMTP, stream->codecs.fmtp,
							JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS, TRUE,
							JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY,
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_MID, janus_rtp_extension_id(JANUS_RTP_EXTMAP_MID),
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_ABS_SEND_TIME, janus_rtp_extension_id(JANUS_RTP_EXTMAP_ABS_SEND_TIME),
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME,
								(session->abscapturetime_src_ext_id > 0 ? janus_rtp_extension_id(JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME) : 0),
							JANUS_SDP_OA_EXTENSION, JANUS_RTP_EXTMAP_PLAYOUT_DELAY,
								(session->playoutdelay_ext ? janus_rtp_extension_id(JANUS_RTP_EXTMAP_PLAYOUT_DELAY) : 0),
							JANUS_SDP_OA_DONE);
					}
#ifdef HAVE_SCTP
					else if(stream->type == JANUS_STREAMING_MEDIA_DATA && data) {
						/* Add data line */
						janus_sdp_generate_offer_mline(offer,
							JANUS_SDP_OA_MLINE, JANUS_SDP_APPLICATION,
							JANUS_SDP_OA_MID, stream->mid,
							JANUS_SDP_OA_DONE);
					}
#endif
					temp = temp->next;
				}
			}
			sdp = janus_sdp_write(offer);
			janus_sdp_destroy(offer);
			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 */
			if(g_list_find(mp->viewers, session) == NULL) {
				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(&session->mutex);
			janus_mutex_unlock(&mp->mutex);
			janus_mutex_unlock(&sessions_mutex);
		} else if(!strcasecmp(request_text, "watch") && jsep_sdp != NULL) {
			/* New subscriber provided an offer, plugin will answer */
			if(sdp_type == NULL || strcasecmp(sdp_type, "offer")) {
				/* This isn't an offer, respond with an error */
				janus_mutex_unlock(&sessions_mutex);
				JANUS_LOG(LOG_ERR, "User provided SDP for a watch request must be an offer\n");
				error_code = JANUS_STREAMING_ERROR_INVALID_SDP;
				g_snprintf(error_cause, 512, "User provided SDP for a watch request must be an offer");
				goto error;
			}
			char error_str[512];
			janus_sdp *parsed_sdp = janus_sdp_parse(jsep_sdp, error_str, sizeof(error_str));
			if(parsed_sdp == NULL) {
				janus_mutex_unlock(&sessions_mutex);
				JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", error_str);
				error_code = JANUS_STREAMING_ERROR_INVALID_SDP;
				g_snprintf(error_cause, 512, "Error parsing SDP: %s", error_str);
				goto error;
			}
			/* When users provide an offer for a "watch", we ignore the media object, as
			 * we'll just match offered m-lines with available streams in the mountpoint;
			 * that said, we still validate the JSON request as for a generic "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) {
				janus_mutex_unlock(&sessions_mutex);
				janus_sdp_destroy(parsed_sdp);
				goto error;
			}
			if(!string_ids) {
				JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			} else {
				JANUS_VALIDATE_JSON_OBJECT(root, idstr_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			}
			if(error_code != 0) {
				janus_mutex_unlock(&sessions_mutex);
				janus_sdp_destroy(parsed_sdp);
				goto error;
			}
			json_t *id = json_object_get(root, "id");
			guint64 id_value = 0;
			char id_num[30], *id_value_str = NULL;
			if(!string_ids) {
				id_value = json_integer_value(id);
				g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
				id_value_str = id_num;
			} else {
				id_value_str = (char *)json_string_value(id);
			}
			/* Find the mountpoint and go on */
			janus_mutex_lock(&mountpoints_mutex);
			janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
				string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
			if(mp == NULL) {
				janus_mutex_unlock(&mountpoints_mutex);
				janus_mutex_unlock(&sessions_mutex);
				janus_sdp_destroy(parsed_sdp);
				JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
				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);
				janus_mutex_unlock(&sessions_mutex);
				janus_sdp_destroy(parsed_sdp);
				goto error;
			}
			janus_mutex_lock(&mp->mutex);
			janus_mutex_lock(&session->mutex);
			janus_mutex_unlock(&mountpoints_mutex);
			if(session->mountpoint) {
				/* Already watching something else */
				JANUS_LOG(LOG_ERR, "Already watching mountpoint %s\n", session->mountpoint->id_str);
				error_code = JANUS_STREAMING_ERROR_INVALID_STATE;
				g_snprintf(error_cause, 512, "Already watching mountpoint %s", session->mountpoint->id_str);
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&mp->mutex);
				janus_mutex_unlock(&sessions_mutex);
				janus_refcount_decrease(&mp->ref);
				janus_sdp_destroy(parsed_sdp);
				goto error;
			}
			if(g_list_find(mp->viewers, session) != NULL) {
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&mp->mutex);
				janus_mutex_unlock(&sessions_mutex);
				janus_refcount_decrease(&mp->ref);
				JANUS_LOG(LOG_ERR, "Already watching a stream (found %p in %s's viewers)...\n", session, id_value_str);
				error_code = JANUS_STREAMING_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "Already watching a stream");
				janus_sdp_destroy(parsed_sdp);
				goto error;
			}
			g_atomic_int_set(&session->stopping, 0);
			session->mountpoint = mp;
			/* Start preparing an answer */
			janus_sdp *answer = janus_sdp_generate_answer(parsed_sdp);
			/* Iterate through the m-lines in the offer, and see if we can find a match */
			GList *subscribed = NULL;
			GList *temp = parsed_sdp->m_lines;
			while(temp) {
				janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
				if(m->direction == JANUS_SDP_INACTIVE || m->direction == JANUS_SDP_SENDONLY) {
					JANUS_LOG(LOG_WARN, "Skipping (%s) m-line (unsupported media direction)\n", janus_sdp_mtype_str(m->type));
					temp = temp->next;
					continue;
				}
				if(mp->streaming_source == janus_streaming_source_file) {
					/* File based streaming, check if we already subscribed to the stream */
					if(m->type == JANUS_SDP_AUDIO) {
						janus_streaming_file_source *source = mp->source;
						int pt = janus_sdp_get_codec_pt(parsed_sdp, m->index, janus_audiocodec_name(source->codecs.audio_codec));
						if(pt != -1) {
							/* Create a session stream */
							janus_streaming_session_stream *s = g_malloc0(sizeof(janus_streaming_session_stream));
							s->mindex = -1;
							s->send = TRUE;
							s->pt = pt;
							janus_rtp_switching_context_reset(&s->context);
							s->min_delay = -1;
							s->max_delay = -1;
							session->streams = g_list_append(session->streams, s);
							if(session->streams_byid == NULL)
								session->streams_byid = g_hash_table_new(NULL, NULL);
							g_hash_table_insert(session->streams_byid, GINT_TO_POINTER(s->mindex), s);
							/* Accept the m-line */
							janus_sdp_generate_answer_mline(parsed_sdp, answer, m,
								JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO,
								JANUS_SDP_OA_CODEC, janus_audiocodec_name(source->codecs.audio_codec),
								JANUS_SDP_OA_FMTP, source->codecs.fmtp,
								JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY,
								JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID,
								JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_ABS_SEND_TIME,
								JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME,
								JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_PLAYOUT_DELAY,
								JANUS_SDP_OA_DONE);
							/* Done */
							subscribed = g_list_append(subscribed, source);
							temp = temp->next;
							continue;
						}
					}
				} else {
					/* Iterate on all media streams, to see if we can find a match */
					gboolean found = FALSE;
					janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)mp->source;
					janus_streaming_rtp_source_stream *stream = NULL;
					janus_streaming_session_stream *s = NULL;
					GList *stemp = source->media;
					while(stemp) {
						stream = (janus_streaming_rtp_source_stream *)stemp->data;
						/* Try matching this stream with this m-line */
						if(g_list_find(subscribed, stream)) {
							/* Stream already matched to a different m-line */
							stemp = stemp->next;
							continue;
						}
						if((m->type == JANUS_SDP_AUDIO && stream->type != JANUS_STREAMING_MEDIA_AUDIO) ||
								(m->type == JANUS_SDP_VIDEO && stream->type != JANUS_STREAMING_MEDIA_VIDEO) ||
								(m->type == JANUS_SDP_APPLICATION && stream->type != JANUS_STREAMING_MEDIA_DATA)) {
							/* Stream is not the same type as the m-line, skip to the next */
							stemp = stemp->next;
							continue;
						}
						int pt = -1;
						const char *codec = NULL;
						if(stream->type != JANUS_STREAMING_MEDIA_DATA) {
							codec = (stream->type == JANUS_STREAMING_MEDIA_AUDIO ?
								janus_audiocodec_name(stream->codecs.audio_codec) : janus_videocodec_name(stream->codecs.video_codec));
							pt = janus_sdp_get_codec_pt(parsed_sdp, m->index, codec);
							if(pt == -1) {
								/* This m-line doesn't support this stream's codec, skip to the next stream */
								stemp = stemp->next;
								continue;
							}
						}
						/* Create a new session stream and add a reference to the source stream */
						s = g_malloc0(sizeof(janus_streaming_session_stream));
						s->mindex = m->index;
						s->send = TRUE;
						s->pt = pt;
						janus_rtp_switching_context_reset(&s->context);
						s->min_delay = -1;
						s->max_delay = -1;
						if(stream && stream->simulcast) {
							/* In case this mountpoint is simulcasting, let's aim high by default */
							janus_rtp_switching_context_reset(&s->context);
							janus_rtp_simulcasting_context_reset(&s->sim_context);
							s->sim_context.substream_target = 2;
							s->sim_context.templayer_target = 2;
							janus_vp8_simulcast_context_reset(&s->vp8_context);
						} else if(stream && stream->svc) {
							/* In case this mountpoint is doing VP9-SVC, let's aim high by default */
							s->spatial_layer = -1;
							s->target_spatial_layer = 2;	/* FIXME Chrome sends 0, 1 and 2 (if using EnabledByFlag_3SL3TL) */
							s->temporal_layer = -1;
							s->target_temporal_layer = 2;	/* FIXME Chrome sends 0, 1 and 2 */
						}
						s->stream = stream;
						janus_refcount_increase(&stream->ref);
						session->streams = g_list_append(session->streams, s);
						if(session->streams_byid == NULL)
							session->streams_byid = g_hash_table_new(NULL, NULL);
						g_hash_table_insert(session->streams_byid, GINT_TO_POINTER(stream->mindex), s);
						/* If this mountpoint is broadcasting end-to-end encrypted media,
						 * add the info to the JSEP offer we'll be sending them */
						session->e2ee = source->e2ee;
						/* Also check if we have to offer the playout-delay extension */
						session->playoutdelay_ext = source->playoutdelay_ext;
						/* Also check if we have to offer the abs-capture-time extension */
						session->abscapturetime_src_ext_id = source->abscapturetime_src_ext_id;
						/* Accept the m-line */
						janus_sdp_generate_answer_mline(parsed_sdp, answer, m,
							JANUS_SDP_OA_MLINE, m->type,
							JANUS_SDP_OA_CODEC, codec,
							JANUS_SDP_OA_FMTP, stream->codecs.fmtp,
							JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS, (m->type == JANUS_SDP_VIDEO),
							JANUS_SDP_OA_DIRECTION, JANUS_SDP_SENDONLY,
							JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID,
							JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_ABS_SEND_TIME,
							JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_ABS_CAPTURE_TIME,
							JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_PLAYOUT_DELAY,
							JANUS_SDP_OA_DONE);
						/* Done */
						subscribed = g_list_append(subscribed, stream);
						found = TRUE;
						break;
					}
					if(found) {
						temp = temp->next;
						continue;
					}
				}
				/* If we got here, the m-line was rejected */
				JANUS_LOG(LOG_WARN, "Skipping %s m-line (no matching mountpoint stream)\n", janus_sdp_mtype_str(m->type));
				temp = temp->next;
			}
			if(subscribed == NULL) {
				/* FIXME Ended up not subscribing to any stream? */
				JANUS_LOG(LOG_WARN, "Not subscribed to any stream (all m-lines rejected)\n");
			} else if(mp->streaming_type == janus_streaming_type_on_demand) {
				/* Spawn a thread */
				GError *error = NULL;
				char tname[16];
				g_snprintf(tname, sizeof(tname), "mp %s", mp->id_str);
				janus_refcount_increase(&session->ref);
				janus_refcount_increase(&mp->ref);
				g_thread_try_new(tname, &janus_streaming_ondemand_thread, session, &error);
				if(error != NULL) {
					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 : "??");
					g_error_free(error);
				}
			}
			g_list_free(subscribed);
			/* Prepare the response */
			sdp_type = "answer";
			sdp = janus_sdp_write(answer);
			janus_sdp_destroy(parsed_sdp);
			janus_sdp_destroy(answer);
			result = json_object();
			json_object_set_new(result, "status", json_string("starting"));
			/* Add the user to the list of watchers and we're done */
			if(g_list_find(mp->viewers, session) == NULL) {
				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_refcount_increase(&session->ref);
			janus_mutex_unlock(&session->mutex);
			janus_mutex_unlock(&mp->mutex);
			janus_mutex_unlock(&sessions_mutex);
		} else if(!strcasecmp(request_text, "start")) {
			janus_mutex_unlock(&sessions_mutex);
			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");
			if(g_atomic_int_get(&session->paused) == 1) {
				/* We were paused: reset the sequence number in RTP packets */
				if(session->streams != NULL) {
					GList *temp = session->streams;
					while(temp) {
						janus_streaming_session_stream *s = (janus_streaming_session_stream *)temp->data;
						if(s != NULL)
							s->context.seq_reset = TRUE;
						temp = temp->next;
					}
				}
			}
			g_atomic_int_set(&session->paused, 0);
			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(g_atomic_int_get(&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(g_atomic_int_get(&session->started) ? "started" : "starting"));
				if(session->mountpoint != NULL)
					json_object_set_new(info, "id", string_ids ?
						json_string(session->mountpoint->id_str) :json_integer(session->mountpoint->id));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
		} else if(!strcasecmp(request_text, "pause")) {
			janus_mutex_unlock(&sessions_mutex);
			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");
			g_atomic_int_set(&session->paused, 1);
			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", string_ids ?
						json_string(session->mountpoint->id_str) : json_integer(session->mountpoint->id));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
		} else if(!strcasecmp(request_text, "configure")) {
			janus_mutex_unlock(&sessions_mutex);
			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);
			/* Audio, video and data are deprecated properties */
			json_t *audio = json_object_get(root, "audio");
			json_t *video = json_object_get(root, "video");
			json_t *data = json_object_get(root, "data");

			/* We use an array of streams to state the changes we want to make,
			 * were for each stream we specify the 'mid' to impact (e.g., send) */
			json_t *streams = json_object_get(root, "streams");
			if(streams == NULL) {
				/* No streams object, check if the properties have been
				 * provided globally, which is how we handled this
				 * request before: if so, create a new fake streams
				 * array, and move the parsed options there */
				streams = json_array();
				json_t *stream = json_object();
				const char *mid = json_string_value(json_object_get(root, "mid"));
				if(mid != NULL)
					json_object_set_new(stream, "mid", json_string(mid));
				json_t *send = json_object_get(root, "send");
				if(send != NULL)
					json_object_set_new(stream, "send", json_is_true(send) ? json_true() : json_false());
				json_t *spatial = json_object_get(root, "spatial_layer");
				if(spatial != NULL)
					json_object_set_new(stream, "spatial_layer", json_integer(json_integer_value(spatial)));
				json_t *sc_substream = json_object_get(root, "substream");
				if(sc_substream != NULL)
					json_object_set_new(stream, "substream", json_integer(json_integer_value(sc_substream)));
				json_t *temporal = json_object_get(root, "temporal_layer");
				if(temporal != NULL)
					json_object_set_new(stream, "temporal_layer", json_integer(json_integer_value(temporal)));
				json_t *sc_temporal = json_object_get(root, "temporal");
				if(sc_temporal != NULL)
					json_object_set_new(stream, "temporal", json_integer(json_integer_value(sc_temporal)));
				json_t *sc_fallback = json_object_get(root, "fallback");
				if(sc_fallback != NULL)
					json_object_set_new(stream, "fallback", json_integer(json_integer_value(sc_fallback)));
				json_t *min_delay = json_object_get(root, "min_delay");
				if(min_delay != NULL)
					json_object_set_new(stream, "min_delay", json_integer(json_integer_value(min_delay)));
				json_t *max_delay = json_object_get(root, "max_delay");
				if(max_delay != NULL)
					json_object_set_new(stream, "max_delay", json_integer(json_integer_value(max_delay)));
				json_array_append_new(streams, stream);
				json_object_set_new(root, "streams", streams);
			}

			size_t i = 0;
			size_t streams_size = json_array_size(streams);
			for(i=0; i<streams_size; i++) {
				json_t *s = json_array_get(streams, i);
				JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
				if(error_code != 0)
					break;
				const char *mid = json_string_value(json_object_get(s, "mid"));
				if(mid == NULL && streams_size > 1) {
					JANUS_LOG(LOG_ERR, "Invalid element (mid can't be null in a streams array)\n");
					error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
					g_snprintf(error_cause, 512, "Invalid value (mid can't be null in a streams array)");
					break;
				}
				if(mid != NULL) {
					json_object_del(root, "audio");
					audio = NULL;
					json_object_del(root, "video");
					video = NULL;
					json_object_del(root, "data");
					data = NULL;
				}
				json_t *spatial = json_object_get(s, "spatial_layer");
				json_t *sc_substream = json_object_get(s, "substream");
				json_t *temporal = json_object_get(s, "temporal_layer");
				json_t *sc_temporal = json_object_get(s, "temporal");
				if(json_integer_value(spatial) < 0 || json_integer_value(spatial) > 2 ||
						json_integer_value(sc_substream) < 0 || json_integer_value(sc_substream) > 2) {
					JANUS_LOG(LOG_ERR, "Invalid element (substream/spatial_layer should be 0, 1 or 2)\n");
					error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
					g_snprintf(error_cause, 512, "Invalid value (substream/spatial_layer should be 0, 1 or 2)");
					break;
				}
				if(json_integer_value(temporal) < 0 || json_integer_value(temporal) > 2 ||
						json_integer_value(sc_temporal) < 0 || json_integer_value(sc_temporal) > 2) {
					JANUS_LOG(LOG_ERR, "Invalid element (temporal/temporal_layer should be 0, 1 or 2)\n");
					error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT;
					g_snprintf(error_cause, 512, "Invalid value (temporal/temporal_layer should be 0, 1 or 2)");
					break;
				}
			}
			if(error_code != 0) {
				goto error;
			}

			if(mp->streaming_source == janus_streaming_source_rtp) {
				/* Enforce the requested changes */
				for(i=0; i<json_array_size(streams); i++) {
					/* Get the stream we need to tweak */
					json_t *sconf = json_array_get(streams, i);
					/* Check which properties we need to tweak */
					const char *mid = json_string_value(json_object_get(sconf, "mid"));
					json_t *send = json_object_get(sconf, "send");
					GList *temp = session->streams;
					while(temp) {
						janus_streaming_session_stream *s = (janus_streaming_session_stream *)temp->data;
						janus_streaming_rtp_source_stream *stream = s->stream;
						/* Check the old and deprecated approach first */
						if(audio && stream->type == JANUS_STREAMING_MEDIA_AUDIO)
							s->send = json_is_true(audio);
						else if(video && stream->type == JANUS_STREAMING_MEDIA_VIDEO)
							s->send = json_is_true(video);
						else if(data && stream->type == JANUS_STREAMING_MEDIA_DATA)
							s->send = json_is_true(data);
						/* Now let's see if this is the right mid */
						if(mid && strcasecmp(stream->mid, mid)) {
							temp = temp->next;
							continue;
						}
						if(send)
							s->send = json_is_true(send);
						if(stream && stream->simulcast) {
							/* Check if the viewer is requesting a different substream/temporal layer */
							json_t *substream = json_object_get(sconf, "substream");
							if(substream) {
								s->sim_context.substream_target = json_integer_value(substream);
								JANUS_LOG(LOG_VERB, "Setting video substream to let through (simulcast): %d (was %d)\n",
									s->sim_context.substream_target, s->sim_context.substream);
								if(s->sim_context.substream_target == s->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(s->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(&stream->need_pli, 1);
								}
							}
							json_t *temporal = json_object_get(sconf, "temporal");
							if(temporal) {
								s->sim_context.templayer_target = json_integer_value(temporal);
								JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n",
									s->sim_context.templayer_target, s->sim_context.templayer);
								if(stream->codecs.video_codec == JANUS_VIDEOCODEC_VP8 && s->sim_context.templayer_target == s->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(s->sim_context.templayer));
									json_object_set_new(event, "result", result);
									gateway->push_event(session->handle, &janus_streaming_plugin, NULL, event, NULL);
									json_decref(event);
								}
							}
							/* Check if we need to change the fallback timer for the substream */
							json_t *fallback = json_object_get(sconf, "fallback");
							if(fallback) {
								JANUS_LOG(LOG_VERB, "Setting fallback timer (simulcast): %lld (was %"SCNu32")\n",
									json_integer_value(fallback) ? json_integer_value(fallback) : 250000,
									s->sim_context.drop_trigger ? s->sim_context.drop_trigger : 250000);
								s->sim_context.drop_trigger = json_integer_value(fallback);
							}
						}
						if(stream && stream->svc) {
							/* Check if the viewer is requesting a different SVC spatial/temporal layer */
							json_t *spatial = json_object_get(sconf, "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 == s->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(s->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 != s->target_spatial_layer) {
									/* Send a FIR to the source, if RTCP is enabled */
									g_atomic_int_set(&stream->need_pli, 1);
								}
								s->target_spatial_layer = spatial_layer;
							}
							json_t *temporal = json_object_get(sconf, "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 == s->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(s->temporal_layer));
									json_object_set_new(event, "result", result);
									gateway->push_event(msg->handle, &janus_streaming_plugin, NULL, event, NULL);
									json_decref(event);
								}
								s->target_temporal_layer = temporal_layer;
							}
						}
						if(stream && stream->type == JANUS_STREAMING_MEDIA_VIDEO && session->playoutdelay_ext) {
							/* Check if we need to specify a custom playout delay for this stream */
							json_t *min_delay = json_object_get(sconf, "min_delay");
							if(min_delay) {
								int16_t md = json_integer_value(min_delay);
								if(md < 0) {
									s->min_delay = -1;
									s->max_delay = -1;
								} else {
									s->min_delay = md;
									if(s->min_delay > s->max_delay)
										s->max_delay = s->min_delay;
								}
							}
							json_t *max_delay = json_object_get(sconf, "max_delay");
							if(max_delay) {
								int16_t md = json_integer_value(max_delay);
								if(md < 0) {
									s->min_delay = -1;
									s->max_delay = -1;
								} else {
									s->max_delay = md;
									if(s->max_delay < s->min_delay)
										s->min_delay = s->max_delay;
								}
							}
						}
						temp = temp->next;
					}
				}
			}
			/* 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.), Besides, it needs
			 * to be mountpoints with the same media in the same order. */
			janus_mutex_lock(&session->mutex);
			janus_streaming_mountpoint *oldmp = session->mountpoint;
			if(oldmp == NULL) {
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&sessions_mutex);
				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_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&sessions_mutex);
				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);
			if(!string_ids) {
				JANUS_VALIDATE_JSON_OBJECT(root, id_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			} else {
				JANUS_VALIDATE_JSON_OBJECT(root, idstr_parameters,
					error_code, error_cause, TRUE,
					JANUS_STREAMING_ERROR_MISSING_ELEMENT, JANUS_STREAMING_ERROR_INVALID_ELEMENT);
			}
			if(error_code != 0) {
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&sessions_mutex);
				janus_refcount_decrease(&oldmp->ref);
				goto error;
			}
			json_t *id = json_object_get(root, "id");
			guint64 id_value = 0;
			char id_num[30], *id_value_str = NULL;
			if(!string_ids) {
				id_value = json_integer_value(id);
				g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id_value);
				id_value_str = id_num;
			} else {
				id_value_str = (char *)json_string_value(id);
			}
			janus_mutex_lock(&mountpoints_mutex);
			janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints,
				string_ids ? (gpointer)id_value_str : (gpointer)&id_value);
			if(mp == NULL || g_atomic_int_get(&mp->destroyed)) {
				janus_mutex_unlock(&mountpoints_mutex);
				janus_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&sessions_mutex);
				JANUS_LOG(LOG_VERB, "No such mountpoint/stream %s\n", id_value_str);
				error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT;
				g_snprintf(error_cause, 512, "No such mountpoint/stream %s", id_value_str);
				goto error;
			}
			janus_refcount_increase(&mp->ref);
			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_mutex_unlock(&session->mutex);
				janus_mutex_unlock(&sessions_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;
			}
			/* TODO: compare the streams of the two mountpoints */
			janus_mutex_unlock(&mountpoints_mutex);
			JANUS_LOG(LOG_VERB, "Request to switch to mountpoint/stream %s (old: %s)\n", mp->id_str, oldmp->id_str);
			g_atomic_int_set(&session->paused, 1);
			/* Unsubscribe from the previous mountpoint and subscribe to the new one */
			session->mountpoint = NULL;
			janus_mutex_unlock(&session->mutex);
			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);
			janus_mutex_lock(&session->mutex);
			janus_refcount_increase(&mp->ref);
			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 = -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_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);
			}
			session->mountpoint = mp;
			/* Send a PLI too, in case the mountpoint supports video and RTCP */
			janus_streaming_rtp_source *source = mp->source;
			GList *temp = source->media;
			while(temp) {
				janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
				if(stream && stream->type == JANUS_STREAMING_MEDIA_VIDEO)
					janus_streaming_rtcp_pli_send(stream);
				temp = temp->next;
			}
			g_atomic_int_set(&session->paused, 0);
			janus_mutex_unlock(&session->mutex);
			janus_mutex_unlock(&mp->mutex);
			janus_mutex_unlock(&sessions_mutex);
			/* Done with the request, remove the references we took for that */
			janus_refcount_decrease(&oldmp->ref);
			janus_refcount_decrease(&mp->ref);
			result = json_object();
			json_object_set_new(result, "switched", json_string("ok"));
			json_object_set_new(result, "id", string_ids ? json_string(id_value_str) : 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", string_ids ? json_string(id_value_str) : json_integer(id_value));
				gateway->notify_event(&janus_streaming_plugin, session->handle, info);
			}
		} else if(!strcasecmp(request_text, "stop")) {
			janus_mutex_unlock(&sessions_mutex);
			if(g_atomic_int_get(&session->stopping) || !g_atomic_int_get(&session->started)) {
				/* Been there, done that: ignore */
				json_t *event = json_object();
				json_object_set_new(event, "streaming", json_string("event"));
				result = json_object();
				json_object_set_new(result, "status", json_string("idle"));
				json_object_set_new(event, "result", result);
				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);
				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", string_ids ? json_string(mp->id_str) : 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_mutex_unlock(&sessions_mutex);
			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);
		}
		g_atomic_int_set(&session->renegotiating, 0);

		/* 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());
		if(session->e2ee)
			json_object_set_new(jsep, "e2ee", 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, char *host, size_t hostlen,
		const char *listenername, const char *medianame, const char *mountpointname, gboolean quiet) {
	janus_mutex_lock(&fd_mutex);
	struct sockaddr_in address = { 0 };
	struct sockaddr_in6 address6 = { 0 };
	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, family = 0;
	while(1) {
		/* By default, we bind to both IPv4 and IPv6, unless IPv6 is disabled */
		family = ipv6_disabled ? AF_INET : 0;
		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;
		}
		if(!use_range) {
			/* Use the port specified in the arguments */
			if(IN_MULTICAST(ntohl(mcast))) {
				fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
				if(fd < 0) {
					JANUS_LOG(LOG_ERR, "[%s] Cannot create socket for %s... %d (%s)\n",
						mountpointname, medianame, errno, g_strerror(errno));
					break;
				}
#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... %d (%s)\n",
						mountpointname, listenername, errno, g_strerror(errno));
					close(fd);
					janus_mutex_unlock(&fd_mutex);
					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)) {
					family = AF_INET;
					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 */
						char *maddr = inet_ntoa(mreq.imr_multiaddr);
						JANUS_LOG(LOG_INFO, "[%s] %s listener using interface address: %s (%s)\n", mountpointname, listenername,
							janus_network_address_string_from_buffer(&address_representation), maddr);
						if(maddr && host && hostlen > 0)
							g_strlcpy(host, maddr, hostlen);
					} else {
						JANUS_LOG(LOG_ERR, "[%s] %s listener: invalid multicast address type (only IPv4 multicast is currently supported by this plugin)\n", mountpointname, listenername);
						close(fd);
						janus_mutex_unlock(&fd_mutex);
						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... %d (%s)\n",
						mountpointname, listenername, errno, g_strerror(errno));
					close(fd);
					janus_mutex_unlock(&fd_mutex);
					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;
		address6.sin6_family = AF_INET6;
		address6.sin6_port = htons(port);
		address6.sin6_addr = in6addr_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... %d (%s)\n",
					mountpointname, listenername, errno, g_strerror(errno));
				close(fd);
				janus_mutex_unlock(&fd_mutex);
				return -1;
			}
			/* TODO IPv6 */
			family = AF_INET;
			address.sin_addr.s_addr = mcast;
		} else {
			if(!IN_MULTICAST(ntohl(mcast)) && !janus_network_address_is_null(iface)) {
				family = iface->family;
				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));
					if(host && hostlen > 0)
						g_strlcpy(host, janus_network_address_string_from_buffer(&address_representation), hostlen);
				} else if(iface->family == AF_INET6) {
					if(ipv6_disabled) {
						JANUS_LOG(LOG_ERR, "[%s] Can't bind to IPv6 address, IPv6 is disabled\n", mountpointname);
						continue;
					}
					memcpy(&address6.sin6_addr, &iface->ipv6, sizeof(iface->ipv6));
					(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));
					if(host && hostlen > 0)
						g_strlcpy(host, janus_network_address_string_from_buffer(&address_representation), hostlen);
				} else {
					JANUS_LOG(LOG_ERR, "[%s] %s listener: invalid address/restriction type\n", mountpointname, listenername);
					continue;
				}
			}
		}
		/* Bind to the specified port */
		if(fd == -1) {
			fd = socket(family == AF_INET ? AF_INET : AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
			int v6only = 0;
			if(fd < 0) {
				JANUS_LOG(LOG_ERR, "[%s] Cannot create socket for %s... %d (%s)\n",
					mountpointname, medianame, errno, g_strerror(errno));
				break;
			}
			if(family != AF_INET && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) {
				JANUS_LOG(LOG_ERR, "[%s] setsockopt on socket failed for %s... %d (%s)\n",
					mountpointname, medianame, errno, g_strerror(errno));
				break;
			}
		}
		size_t addrlen = (family == AF_INET ? sizeof(address) : sizeof(address6));
		if(bind(fd, (family == AF_INET ? (struct sockaddr *)&address : (struct sockaddr *)&address6), addrlen) < 0) {
			close(fd);
			fd = -1;
			if(!quiet) {
				JANUS_LOG(LOG_ERR, "[%s] Bind failed for %s (port %d)... %d (%s)\n",
					mountpointname, medianame, port, errno, g_strerror(errno));
			}
			if(!use_range)	/* Asked for a specific port but it's not available, give up */
				break;
		} else {
			if(use_range)
				rtp_range_slider = port;	/* Update global slider */
			break;
		}
	}
	janus_mutex_unlock(&fd_mutex);
	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, NULL, 0, media, media, name, TRUE);
		if(rtp_fd != -1) {
			rtcp_fd = janus_streaming_create_fd(rtcp_port, mcast, iface, NULL, 0, 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_in6 server = { 0 };
	socklen_t len = sizeof(server);
	if(getsockname(fd, (struct sockaddr *)&server, &len) == -1) {
		return -1;
	}

	return ntohs(server.sin6_port);
}

/* Helpers to destroy a streaming mountpoint source */
static void janus_streaming_rtp_source_stream_free(const janus_refcount *st_ref) {
	janus_streaming_rtp_source_stream *stream = janus_refcount_containerof(st_ref, janus_streaming_rtp_source_stream, ref);
	/* This stream can be destroyed, free all the resources */
	if(stream->fd[0] > -1)
		close(stream->fd[0]);
	if(stream->fd[1] > -1)
		close(stream->fd[1]);
	if(stream->fd[2] > -1)
		close(stream->fd[2]);
	if(stream->rtcp_fd > -1)
		close(stream->rtcp_fd);
	g_free(stream->host);
	janus_mutex_lock(&stream->keyframe.mutex);
	if(stream->keyframe.latest_keyframe != NULL)
		g_list_free_full(stream->keyframe.latest_keyframe, (GDestroyNotify)janus_streaming_rtp_relay_packet_free);
	janus_mutex_unlock(&stream->keyframe.mutex);
	janus_mutex_lock(&stream->buffermsg_mutex);
	if(stream->last_msg != NULL)
		janus_streaming_rtp_relay_packet_free((janus_streaming_rtp_relay_packet *)stream->last_msg);
	stream->last_msg = NULL;
	janus_mutex_unlock(&stream->buffermsg_mutex);
	g_free(stream->codecs.fmtp);
	g_free(stream->h264_spspps);
	g_free(stream->mid);
	g_free(stream->label);
	g_free(stream->msid);
	g_free(stream->mstid);
	g_free(stream->mcast_str);
	g_free(stream->iface_str);
	if(stream->rc != NULL) {
		janus_recorder_close(stream->rc);
		janus_recorder_destroy(stream->rc);
	}
	g_free(stream);
}

/* Helpers to destroy a streaming mountpoint. */
static void janus_streaming_rtp_source_free(gpointer data) {
	janus_streaming_rtp_source *source = (janus_streaming_rtp_source *)data;
	if(source->pipefd[0] > -1) {
		close(source->pipefd[0]);
	}
	if(source->pipefd[1] > -1) {
		close(source->pipefd[1]);
	}
	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);
		g_free(source->curl_errbuf);
	}
	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_stream_uri);
	g_free(source->rtsp_ahost);
	g_free(source->rtsp_vhost);
	g_free(source->rtsp_vcodecs.fmtp);
	janus_mutex_unlock(&source->rtsp_mutex);
	janus_mutex_destroy(&source->rtsp_mutex);
#endif
	g_list_free_full(source->media, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
	g_hash_table_unref(source->media_byid);
	g_hash_table_unref(source->media_byfd);
	janus_mutex_destroy(&source->rec_mutex);
	g_free(source);
}

static void janus_streaming_file_source_free(gpointer data) {
	janus_streaming_file_source *source = (janus_streaming_file_source *)data;
	g_free(source->codecs.fmtp);
	g_free(source->filename);
	g_free(source);
}

/* Helper to create an RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
/* Helpers to create an RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */
janus_streaming_rtp_source_stream *janus_streaming_create_rtp_source_stream(
		const char *name, int mindex, const char *type, const char *mid, const char *label, const char *msid,
		char *mcast, char *miface, const janus_network_address *iface,
		uint16_t port, uint16_t port2, uint16_t port3, gboolean dortcp, uint16_t rtcpport,
		uint8_t pt, char *codec, char *fmtp, char *sprop,
		gboolean doskew, uint16_t bufferkf_ms, uint32_t bufferkf_bytes, gboolean simulcast, gboolean svc,
		gboolean textdata, gboolean buffermsg) {
	if(type == NULL || mid == NULL || label == NULL) {
		JANUS_LOG(LOG_ERR, "[%s] Can't add 'rtp' stream, missing media type, mid or label...\n", name);
		return NULL;
	}
	janus_streaming_media mtype = janus_streaming_parse_media(type);
	if(mtype == JANUS_STREAMING_MEDIA_NONE) {
		JANUS_LOG(LOG_ERR, "[%s] Can't add 'rtp' stream, invalid type '%s'...\n", name, type);
		return NULL;
	}
#ifndef HAVE_SCTP
	if(mtype == JANUS_STREAMING_MEDIA_DATA) {
		JANUS_LOG(LOG_WARN, "[%s] Mountpoint wants to do datachannel relaying, but datachannels support was not compiled...\n", name);
		return NULL;
	}
#endif
	if(mtype != JANUS_STREAMING_MEDIA_DATA && (codec == NULL)) {
		JANUS_LOG(LOG_ERR, "[%s] Can't add 'rtp' stream, missing mandatory information for %s...\n", name, type);
		return NULL;
	}
	/* Allocate file descriptors (if video, we may be simulcasting) */
	int fd[3] = {-1, -1, -1};
	int rtcp_fd = -1;
	char host[46];
	host[0] = '\0';
	fd[0] = janus_streaming_create_fd(port, mcast ? inet_addr(mcast) : INADDR_ANY, iface,
		host, sizeof(host), type, type, name, port == 0);
	if(fd[0] < 0) {
		JANUS_LOG(LOG_ERR, "[%s] Can't bind to port %d...\n", name, port);
		return NULL;
	}
	port = janus_streaming_get_fd_port(fd[0]);
	if(dortcp) {
		rtcp_fd = janus_streaming_create_fd(rtcpport, mcast ? inet_addr(mcast) : INADDR_ANY, iface,
			NULL, 0, type, type, name, rtcpport == 0);
		if(rtcp_fd < 0) {
			JANUS_LOG(LOG_ERR, "[%s] Can't bind to port %d for RTCP...\n", name, rtcpport);
			if(fd[0] > -1)
				close(fd[0]);
			return NULL;
		}
		rtcpport = janus_streaming_get_fd_port(rtcp_fd);
	}
	if(mtype == JANUS_STREAMING_MEDIA_VIDEO) {
		if(simulcast) {
			fd[1] = janus_streaming_create_fd(port2, mcast ? inet_addr(mcast) : INADDR_ANY, iface,
				NULL, 0, "Video", "video", name, FALSE);
			if(fd[1] < 0) {
				JANUS_LOG(LOG_ERR, "[%s] Can't bind to port %d for video (2nd port)...\n", name, port2);
				if(fd[0] > -1)
					close(fd[0]);
				if(rtcp_fd > -1)
					close(rtcp_fd);
				return NULL;
			}
			port2 = janus_streaming_get_fd_port(fd[1]);
			fd[2] = janus_streaming_create_fd(port3, mcast ? inet_addr(mcast) : INADDR_ANY, iface,
				NULL, 0, "Video", "video", name, FALSE);
			if(fd[2] < 0) {
				JANUS_LOG(LOG_ERR, "[%s] Can't bind to port %d for video (3rd port)...\n", name, port3);
				if(fd[0] > -1)
					close(fd[0]);
				if(rtcp_fd > -1)
					close(rtcp_fd);
				if(fd[1] > -1)
					close(fd[1]);
				return NULL;
			}
			port3 = janus_streaming_get_fd_port(fd[2]);
		}
	}
	/* Create the stream */
	janus_network_address nil;
	janus_network_address_nullify(&nil);

	janus_streaming_rtp_source_stream *stream = g_malloc0(sizeof(janus_streaming_rtp_source_stream));
	stream->mindex = mindex;
	stream->type = mtype;
	stream->mid = g_strdup(mid);
	stream->label = g_strdup(label);
	if(msid) {
		char s_msid[65], s_mstid[65];
		s_msid[0] = '\0';
		s_mstid[0] = '\0';
		if(sscanf(msid, "%64s %64s", s_msid, s_mstid) != 2) {
			JANUS_LOG(LOG_WARN, "[%s] Invalid msid for stream, ignoring\n", name);
		} else {
			stream->msid = g_strdup(s_msid);
			stream->mstid = g_strdup(s_mstid);
		}
	}
	stream->codecs.pt = (mtype != JANUS_STREAMING_MEDIA_DATA ? pt : -1);
	stream->codecs.fmtp = fmtp ? g_strdup(fmtp) : NULL;
	stream->codecs.audio_codec = JANUS_AUDIOCODEC_NONE;
	if(mtype == JANUS_STREAMING_MEDIA_AUDIO)
		stream->codecs.audio_codec = janus_audiocodec_from_name(codec);
	stream->codecs.video_codec = JANUS_VIDEOCODEC_NONE;
	if(mtype == JANUS_STREAMING_MEDIA_VIDEO) {
		stream->codecs.video_codec = janus_videocodec_from_name(codec);
		if(svc) {
			if(stream->codecs.video_codec == JANUS_VIDEOCODEC_VP9) {
				stream->svc = TRUE;
			} else {
				JANUS_LOG(LOG_WARN, "[%s] SVC is only supported, in an experimental way, for VP9-SVC mountpoints: disabling it...\n", name);
			}
		}
		if(sprop && stream->codecs.video_codec == JANUS_VIDEOCODEC_H264) {
			/* Create a fake RTP packet out of the sprop-parameter-sets */
			int spslen = 0;
			char *sps = janus_streaming_parse_sprop(sprop, &spslen);
			if(sps == NULL) {
				JANUS_LOG(LOG_WARN, "Error parsing sprop-parameter-sets value, ignoring...\n");
			} else {
				stream->h264_spspps = sps;
				stream->h264_spspps_len = spslen;
			}
		}
	}
	stream->simulcast = (mtype == JANUS_STREAMING_MEDIA_VIDEO && simulcast);
	stream->mcast = mcast ? inet_addr(mcast) : INADDR_ANY;
	stream->mcast_str = mcast ? g_strdup(mcast) : NULL;
	if(strlen(host) > 0)
		stream->host = g_strdup(host);
	stream->port[0] = port;
	stream->rtcp_port = rtcpport ? rtcpport : -1;
	stream->port[1] = stream->simulcast ? port2 : -1;
	stream->port[2] = stream->simulcast ? port3 : -1;
	stream->iface = !janus_network_address_is_null(iface) ? *iface : nil;
	stream->iface_str = miface ? g_strdup(miface) : NULL;
	stream->skew = doskew;
	stream->rc = NULL;
	janus_rtp_switching_context_reset(&stream->context[0]);
	janus_rtp_switching_context_reset(&stream->context[1]);
	janus_rtp_switching_context_reset(&stream->context[2]);
	stream->fd[0] = fd[0];
	stream->fd[1] = fd[1];
	stream->fd[2] = fd[2];
	stream->rtcp_fd = rtcp_fd;
	stream->last_received[0] = janus_get_monotonic_time();
	stream->last_received[1] = stream->last_received[0];
	stream->last_received[2] = stream->last_received[0];
	if(mtype == JANUS_STREAMING_MEDIA_VIDEO) {
		stream->keyframe.enabled = (bufferkf_ms > 0 || bufferkf_bytes > 0);
		stream->keyframe.bufferkf_ms = bufferkf_ms;
		stream->keyframe.bufferkf_bytes = bufferkf_bytes;
		stream->keyframe.latest_keyframe = NULL;
		stream->keyframe.kf_ssrc = 0;
		stream->keyframe.kf_ts = 0;
		stream->keyframe.kf_bytes = 0;
		stream->keyframe.kf_start = 0;
		stream->keyframe.first_ts = FALSE;
		janus_mutex_init(&stream->keyframe.mutex);
	} else if(mtype == JANUS_STREAMING_MEDIA_DATA) {
		stream->textdata = textdata;
		stream->buffermsg = buffermsg;
		stream->last_msg = NULL;
		janus_mutex_init(&stream->buffermsg_mutex);
	}
	janus_refcount_init(&stream->ref, janus_streaming_rtp_source_stream_free);
	return stream;
}

janus_streaming_mountpoint *janus_streaming_create_rtp_source(
		uint64_t id, char *id_str, char *name, char *desc, char *metadata,
		GList *media, int srtpsuite, char *srtpcrypto, int threads, int rtp_collision,
		uint16_t bufferkf_ms, uint32_t bufferkf_bytes,
		gboolean e2ee, gboolean playoutdelay_ext, int abscapturetime_src_ext_id) {
	char id_num[30];
	if(!string_ids) {
		g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id);
		id_str = id_num;
	}
	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-%s", id_str);
	} 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(!media || g_list_length(media) == 0) {
		JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint, no audio, video or data have to be streamed...\n");
		janus_mutex_unlock(&mountpoints_mutex);
		return NULL;
	}
	/* 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->id_str = g_strdup(id_str);
	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->metadata = (metadata ? g_strdup(metadata) : NULL);
	live_rtp->enabled = TRUE;
	live_rtp->active = FALSE;
	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);
			g_list_free_full(media, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
			g_free(live_rtp_source);
			g_free(live_rtp->name);
			g_free(live_rtp->description);
			g_free(live_rtp->metadata);
			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);
			g_list_free_full(media, (GDestroyNotify)(janus_streaming_rtp_source_stream_unref));
			g_free(live_rtp_source);
			g_free(live_rtp->name);
			g_free(live_rtp->description);
			g_free(live_rtp->metadata);
			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->audio = FALSE;
	live_rtp->video = FALSE;
	live_rtp->data = FALSE;
	live_rtp_source->media = media;
	live_rtp_source->media_byid = g_hash_table_new(NULL, NULL);
	live_rtp_source->media_byfd = g_hash_table_new(NULL, NULL);
	GList *temp = media;
	while(temp) {
		janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
		if(stream->type == JANUS_STREAMING_MEDIA_AUDIO)
			live_rtp->audio = TRUE;
		else if(stream->type == JANUS_STREAMING_MEDIA_VIDEO)
			live_rtp->video = TRUE;
		else if(stream->type == JANUS_STREAMING_MEDIA_DATA)
			live_rtp->data = TRUE;
		/* Map this stream by mindex */
		g_hash_table_insert(live_rtp_source->media_byid, GINT_TO_POINTER(stream->mindex), stream);
		/* Map this stream by all its file descriptors */
		if(stream->fd[0] != -1)
			g_hash_table_insert(live_rtp_source->media_byfd, GINT_TO_POINTER(stream->fd[0]), stream);
		if(stream->fd[1] != -1)
			g_hash_table_insert(live_rtp_source->media_byfd, GINT_TO_POINTER(stream->fd[1]), stream);
		if(stream->fd[2] != -1)
			g_hash_table_insert(live_rtp_source->media_byfd, GINT_TO_POINTER(stream->fd[2]), stream);
		if(stream->rtcp_fd != -1)
			g_hash_table_insert(live_rtp_source->media_byfd, GINT_TO_POINTER(stream->rtcp_fd), stream);
		temp = temp->next;
	}
	live_rtp_source->pipefd[0] = -1;
	live_rtp_source->pipefd[1] = -1;
	pipe(live_rtp_source->pipefd);
	janus_mutex_init(&live_rtp_source->rec_mutex);
	live_rtp_source->rtp_collision = rtp_collision;
	live_rtp_source->bufferkf_ms = bufferkf_ms;
	live_rtp_source->bufferkf_bytes = bufferkf_bytes;
	live_rtp_source->e2ee = e2ee;
	live_rtp_source->playoutdelay_ext = playoutdelay_ext;
	live_rtp_source->abscapturetime_src_ext_id = abscapturetime_src_ext_id;
	live_rtp->source = live_rtp_source;
	live_rtp->source_destroy = (GDestroyNotify)janus_streaming_rtp_source_free;
	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);
	janus_mutex_lock(&mountpoints_mutex);
	g_hash_table_insert(mountpoints,
		string_ids ? (gpointer)g_strdup(live_rtp->id_str) : (gpointer)janus_uint64_dup(live_rtp->id),
		live_rtp);
	g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)live_rtp->id_str : (gpointer)&live_rtp->id);
	/* 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);
			janus_refcount_init(&helper->ref, janus_streaming_helper_free);
			live_rtp->helper_threads++;
			/* Spawn a thread and add references */
			g_snprintf(tname, sizeof(tname), "help %u-%"SCNu64, helper->id, live_rtp->id);
			janus_refcount_increase(&live_rtp->ref);
			janus_refcount_increase(&helper->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 : "??");
				g_error_free(error);
				janus_refcount_decrease(&live_rtp->ref);	/* This is for the helper thread */
				g_async_queue_unref(helper->queued_packets);
				janus_refcount_decrease(&helper->ref);
				/* This extra unref is for the init */
				janus_refcount_decrease(&helper->ref);
				janus_mutex_unlock(&mountpoints_mutex);
				janus_streaming_mountpoint_destroy(live_rtp);
				return NULL;
			}
			janus_refcount_increase(&helper->ref);
			live_rtp->threads = g_list_append(live_rtp->threads, helper);
		}
	}
	janus_mutex_unlock(&mountpoints_mutex);
	/* Finally, create the mountpoint thread itself */
	g_snprintf(tname, sizeof(tname), "mp %s", live_rtp->id_str);
	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 : "??");
		g_error_free(error);
		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 *id_str, char *name, char *desc, char *metadata, char *filename, gboolean live,
		gboolean doaudio, uint8_t apt, char *acodec, char *afmtp, gboolean dovideo) {
	char id_num[30];
	if(!string_ids) {
		g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id);
		id_str = id_num;
	}
	if(filename == NULL) {
		JANUS_LOG(LOG_ERR, "Can't add 'live' stream, missing filename...\n");
		return NULL;
	}
	if(name == NULL) {
		JANUS_LOG(LOG_VERB, "Missing name, will generate a random one...\n");
	}
	if(!doaudio && !dovideo) {
		JANUS_LOG(LOG_ERR, "Can't add 'file' stream, no audio or video have to be streamed...\n");
		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");
		return NULL;
	}
	/* TODO We should support something more than raw a-Law and mu-Law streams... */
#ifdef HAVE_LIBOGG
	if(!janus_streaming_check_extension(filename, ".opus") &&
			!janus_streaming_check_extension(filename, ".alaw") &&
			!janus_streaming_check_extension(filename, ".mulaw")) {
		JANUS_LOG(LOG_ERR, "Can't add 'file' stream, unsupported format (we only support Opus and raw mu-Law/a-Law files right now)\n");
#else
	if(!janus_streaming_check_extension(filename, ".alaw") &&
			!janus_streaming_check_extension(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");
#endif
		return NULL;
	}
	janus_audiocodec audio_codec = janus_audiocodec_from_name(acodec);
#ifdef HAVE_LIBOGG
	if(janus_streaming_check_extension(filename, ".opus") && audio_codec != JANUS_AUDIOCODEC_OPUS) {
		JANUS_LOG(LOG_ERR, "Can't add 'file' stream, opus file is not associated with an opus rtpmap\n");
		return NULL;
	}
#endif
	janus_streaming_mountpoint *file_source = g_malloc0(sizeof(janus_streaming_mountpoint));
	file_source->id = id;
	file_source->id_str = g_strdup(id_str);
	char tempname[255];
	if(!name) {
		memset(tempname, 0, 255);
		g_snprintf(tempname, 255, "mp-%s", file_source->id_str);
	} 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->metadata = (metadata ? g_strdup(metadata) : NULL);
	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;
	if(janus_streaming_check_extension(filename, ".opus")) {
		file_source_source->opus = TRUE;
		file_source_source->codecs.pt = apt;
		file_source_source->codecs.audio_codec = JANUS_AUDIOCODEC_OPUS;
		file_source_source->codecs.fmtp = afmtp ? g_strdup(afmtp) : NULL;
	} else {
		file_source_source->codecs.pt = janus_streaming_check_extension(filename, ".alaw") ? 8 : 0;
		file_source_source->codecs.audio_codec = janus_streaming_check_extension(filename, ".alaw") ? JANUS_AUDIOCODEC_PCMA : JANUS_AUDIOCODEC_PCMU;
	}
	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);
	janus_mutex_lock(&mountpoints_mutex);
	g_hash_table_insert(mountpoints,
		string_ids ? (gpointer)g_strdup(file_source->id_str) : (gpointer)janus_uint64_dup(file_source->id),
		file_source);
	g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)file_source->id_str : (gpointer)&file_source->id);
	janus_mutex_unlock(&mountpoints_mutex);
	if(live) {
		GError *error = NULL;
		char tname[16];
		g_snprintf(tname, sizeof(tname), "mp %s", file_source->id_str);
		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 : "??");
			g_error_free(error);
			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,
		char *base, int *pt, char *transport, char *host,
		char *rtpmap, char *fmtp, char *control, char **sps, int *spslen,
		const janus_network_address *iface, multiple_fds *fds) {
	/* Start by checking if there's any Content-Base header we should be aware of */
	const char *cb = strstr(buffer, "Content-Base:");
	if(cb == NULL)
		cb = strstr(buffer, "content-base:");
	if(cb != NULL) {
		cb = strstr(cb, "rtsp://");
		const char *crlf = (cb ? strstr(cb, "\r\n") : NULL);
		if(crlf != NULL && base != NULL) {
			gulong size = (crlf-cb)+1;
			if(size > 256)
				size = 256;
			g_snprintf(base, size, "%s", cb);
			if(base[size-2] == '/')
				base[size-2] = '\0';
		}
	}
	/* Parse the SDP now */
	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) {
		if(sscanf(r, "a=rtpmap:%*d%*[ ]%2047[^\r\n]s", rtpmap) != 1) {
			JANUS_LOG(LOG_ERR, "[%s] cannot parse %s rtpmap...\n", name, media);
			return -1;
		}
	}
	char *f = strstr(m, "a=fmtp:");
	if(f != NULL) {
		if(sscanf(f, "a=fmtp:%*d%*[ ]%2047[^\r\n]s", fmtp) != 1) {
			JANUS_LOG(LOG_ERR, "[%s] cannot parse %s fmtp...\n", name, media);
			return -1;
		}
		char *start = strstr(f, "sprop-parameter-sets=");
		if(sps != NULL && start != NULL) {
			start += strlen("sprop-parameter-sets=");
			char *end = strstr(start, ";");
			if(end == NULL)
				end = strstr(start, "\r");
			if(end == NULL)
				end = strstr(start, "\n");
			if(end) {
				char c = *end;
				*end = '\0';
				JANUS_LOG(LOG_VERB, "[%s] Found sprop-parameter-sets: %s\n", name, start);
				*sps = janus_streaming_parse_sprop(start, spslen);
				if(*sps == NULL)
					JANUS_LOG(LOG_WARN, "[%s] Error parsing sprop-parameter-sets value, ignoring...\n", name);
				*end = c;
			}
		}
	}
	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 %255[^/]", 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;
}

/* Helper function to calculating the minimum value if 'a' is bigger than zero */
static inline gint64 janus_streaming_min_if(gint64 a, gint64 b) {
	return a > 0 ? (a > b ? b : a) : b;
}

/* 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, source->rtsp_timeout);
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, source->rtsp_conn_timeout);
	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 0L);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
#if CURL_AT_LEAST_VERSION(7, 66, 0)
#if CURL_AT_LEAST_VERSION(7, 85, 0)
	curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "rtsp");
#else
	curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_RTSP);
#endif
	curl_easy_setopt(curl, CURLOPT_HTTP09_ALLOWED, 1L);
#endif
	char *curl_errbuf = g_malloc(CURL_ERROR_SIZE);
	*curl_errbuf = '\0';
	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
	/* 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 (%s)\n",
			curl_easy_strerror(res), curl_errbuf);
		curl_easy_cleanup(curl);
		g_free(curl_errbuf);
		g_free(curldata->buffer);
		g_free(curldata);
		return -2;
	}
	long code = 0;
	res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
#if CURL_AT_LEAST_VERSION(7, 62, 0)
	if(source->rtsp_quirk && code == 404) {
		/* Possibly a quirk in the RTSP server, where the DESCRIBE request expects a path only. */
		CURLU *curl_u = curl_url();
		char *path = NULL;
		if(curl_u && !(curl_url_set(curl_u, CURLUPART_URL, source->rtsp_url, 0))) {
			if(!(curl_url_get(curl_u, CURLUPART_PATH, &path, 0))) {
				curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, path);
				res = curl_easy_perform(curl);
				if(res == CURLE_OK) {
					res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
					if((res == CURLE_OK) && (code != 404)) {
						source->rtsp_stream_uri = g_strdup(path);
					}
				}
				curl_free(path);
			}
		}
		curl_url_cleanup(curl_u);
	}
#endif
	if(res != CURLE_OK) {
		JANUS_LOG(LOG_ERR, "Couldn't get DESCRIBE answer: %s (%s)\n",
			curl_easy_strerror(res), curl_errbuf);
		curl_easy_cleanup(curl);
		g_free(curl_errbuf);
		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(curl_errbuf);
		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 vpt = -1;
	char vrtpmap[2048];
	vrtpmap[0] = '\0';
	char vfmtp[2048];
	vfmtp[0] = '\0';
	char vcontrol[2048];
	char uri[1024];
	char vtransport[1024];
	char vhost[256];
	vhost[0] = '\0';
	char vbase[256];
	vbase[0] = '\0';
	int vsport = 0, vsport_rtcp = 0;
	multiple_fds video_fds = {-1, -1};

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

	while (!janus_mutex_trylock(&mountpoints_mutex)) {
		if(g_atomic_int_get(&mp->destroyed)) {
			JANUS_LOG(LOG_WARN, "[%s] Destroying mountpoint while trying to reconnect, aborting\n", mp->name);
			curl_easy_cleanup(curl);
			g_free(curl_errbuf);
			g_free(curldata->buffer);
			g_free(curldata);
			return -8;
		}

		g_usleep(1000);
	}

	/* Parse both video and audio first before proceed to setup as curldata will be reused */
	janus_network_address audio_iface = { 0 }, video_iface = { 0 };
	uint32_t audio_ssrc = 0, video_ssrc = 0;
	int spslen = 0;
	char *sps = NULL;
	int vresult = -1;
	if(dovideo) {
		vresult = janus_streaming_rtsp_parse_sdp(curldata->buffer, name, "video", vbase, &vpt,
			vtransport, vhost, vrtpmap, vfmtp, vcontrol, &sps, &spslen, &video_iface, &video_fds);
	}
	int aresult = -1;
	if(doaudio) {
		aresult = janus_streaming_rtsp_parse_sdp(curldata->buffer, name, "audio", abase, &apt,
			atransport, ahost, artpmap, afmtp, acontrol, NULL, NULL, &audio_iface, &audio_fds);
	}
	janus_mutex_unlock(&mountpoints_mutex);

	if(vresult == -1 && aresult == -1) {
		/* Both audio and video failed? Give up... */
		curl_easy_cleanup(curl);
		g_free(curl_errbuf);
		g_free(curldata->buffer);
		g_free(curldata);
		return -7;
	}

	/* Check if a query string is part of the URL, as that may impact the SETUP request */
	char *rtsp_url = source->rtsp_url, *rtsp_querystring = NULL;
	char **parts = g_strsplit(source->rtsp_url, "?", 2);
	if(parts[0] != NULL) {
		rtsp_url = parts[0];
		rtsp_querystring = parts[1];
	}

	janus_videocodec video_codec = JANUS_VIDEOCODEC_NONE;
	if(vresult != -1) {
		/* Identify video codec (useful for keyframe detection) */
		video_codec = janus_videocodec_from_name(janus_sdp_get_rtpmap_codec(vrtpmap));
		/* Send an RTSP SETUP for video */
		g_free(curldata->buffer);
		curldata->buffer = g_malloc0(1);
		curldata->size = 0;
		gboolean add_qs = (rtsp_querystring != NULL);
		if(add_qs && strstr(vcontrol, rtsp_querystring) != NULL)
			add_qs = FALSE;
		if(strstr(vcontrol, (strlen(vbase) > 0 ? vbase : rtsp_url)) == vcontrol) {
			/* The control attribute already contains the whole URL? */
			g_snprintf(uri, sizeof(uri), "%s%s%s", vcontrol,
				add_qs ? "?" : "", add_qs ? rtsp_querystring : "");
		} else {
			/* Append the control attribute to the URL */
			g_snprintf(uri, sizeof(uri), "%s/%s%s%s", (strlen(vbase) > 0 ? vbase : rtsp_url),
				vcontrol, add_qs ? "?" : "", add_qs ? rtsp_querystring : "");
		}
		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 (%s)\n",
				curl_easy_strerror(res), curl_errbuf);
			g_strfreev(parts);
			curl_easy_cleanup(curl);
			g_free(curl_errbuf);
			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;
		}
		res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
		if(code != 200) {
			JANUS_LOG(LOG_ERR, "Couldn't SETUP, got error code: %ld\n", code);
			g_strfreev(parts);
			curl_easy_cleanup(curl);
			g_free(curl_errbuf);
			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);
		/* Parse the RTSP message: we may need Transport and Session */
		gboolean success = TRUE;
		gchar **parts = g_strsplit(curldata->buffer, "\n", -1);
		if(parts) {
			int index = 0;
			char *line = NULL, *cr = NULL;
			while(success && (line = parts[index]) != NULL) {
				cr = strchr(line, '\r');
				if(cr != NULL)
					*cr = '\0';
				if(*line == '\0') {
					if(cr != NULL)
						*cr = '\r';
					index++;
					continue;
				}
				if(strnlen(line, 3) < 3) {
					JANUS_LOG(LOG_ERR, "Invalid RTSP line (%zu bytes): %s\n", strlen(line), line);
					success = FALSE;
					break;
				}
				/* Check if this is a Transport or Session header, and if so parse it */
				gboolean is_transport = (strstr(line, "Transport:") == line || strstr(line, "transport:") == line);
				gboolean is_session = (strstr(line, "Session:") == line || strstr(line, "session:") == line);
				if(is_transport || is_session) {
					/* There is, iterate on all params */
					char *p = line, param[100], *pi = NULL;
					int read = 0;
					gboolean first = TRUE;
					while(sscanf(p, "%99[^;]%n", param, &read) == 1) {
						if(first) {
							/* Skip */
							first = FALSE;
						} else {
							pi = param;
							while(*pi == ' ')
								pi++;
							char name[50], value[50];
							if(sscanf(pi, "%49[a-zA-Z_0-9]=%49s", name, value) == 2) {
								if(is_transport) {
									if(!strcasecmp(name, "ssrc")) {
										/* Take note of the video SSRC */
										video_ssrc = strtol(value, NULL, 16);
										JANUS_LOG(LOG_VERB, "  -- SSRC (video): %"SCNu32"\n", video_ssrc);
									} else if(!strcasecmp(name, "source")) {
										/* If we got an address via c-line, replace it */
										g_snprintf(vhost, sizeof(vhost), "%s", value);
										JANUS_LOG(LOG_VERB, "  -- Source (video): %s\n", vhost);
									} else if(!strcasecmp(name, "server_port")) {
										/* Take note of the server port */
										char *dash = NULL;
										vsport = strtol(value, &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);
									}
								} else if(is_session) {
									if(!strcasecmp(name, "timeout")) {
										/* Take note of the timeout, for keep-alive */
										source->ka_timeout = janus_streaming_min_if(source->session_timeout, (gint64)atoi(value) / 2 * G_USEC_PER_SEC);
										JANUS_LOG(LOG_VERB, "  -- RTSP session timeout (video): %"SCNi64" ms\n", source->ka_timeout / 1000);
									}
								}
							}
						}
						/* Move to the next param */
						p += read;
						if(*p != ';')
							break;
						while(*p == ';')
							p++;
					}
				}
				if(cr != NULL)
					*cr = '\r';
				index++;
			}
			if(cr != NULL)
				*cr = '\r';
			g_strfreev(parts);
		}
#ifdef HAVE_LIBCURL
#if CURL_AT_LEAST_VERSION(7, 62, 0)
		/* If we don't have a host yet (no c-line, no source in Transport), use the server address */
		if(strlen(vhost) == 0 || !strcmp(vhost, "0.0.0.0")) {
			JANUS_LOG(LOG_WARN, "No c-line or source for RTSP video address, resolving server address...\n");
			CURLU *url = curl_url();
			if(url != NULL) {
				CURLUcode code = curl_url_set(url, CURLUPART_URL, source->rtsp_url, 0);
				if(code == 0) {
					char *host = NULL;
					code = curl_url_get(url, CURLUPART_HOST, &host, 0);
					if(code == 0) {
						/* Resolve the address */
						struct addrinfo *info = NULL, *start = NULL;
						janus_network_address addr;
						janus_network_address_string_buffer addr_buf;
						if(getaddrinfo(host, NULL, NULL, &info) == 0) {
							start = info;
							while(info != NULL) {
								if(janus_network_address_from_sockaddr(info->ai_addr, &addr) == 0 &&
										janus_network_address_to_string_buffer(&addr, &addr_buf) == 0) {
									/* Resolved */
									g_snprintf(vhost, sizeof(vhost), "%s",
										janus_network_address_string_from_buffer(&addr_buf));
									JANUS_LOG(LOG_VERB, "   -- %s\n", vhost);
									break;
								}
								info = info->ai_next;
							}
						}
						if(start)
							freeaddrinfo(start);
						curl_free(host);
					}
				}
				curl_url_cleanup(url);
			}
		}
#endif
#endif
		if(strlen(vhost) == 0 || !strcmp(vhost, "0.0.0.0")) {
			/* Still nothing... */
			JANUS_LOG(LOG_WARN, "No host address for the RTSP video stream, no latching will be performed\n");
		}
	}

	janus_audiocodec audio_codec = JANUS_AUDIOCODEC_NONE;
	if(aresult != -1) {
		/* Identify audio codec */
		audio_codec = janus_audiocodec_from_name(janus_sdp_get_rtpmap_codec(artpmap));
		/* Send an RTSP SETUP for audio */
		g_free(curldata->buffer);
		curldata->buffer = g_malloc0(1);
		curldata->size = 0;
		gboolean add_qs = (rtsp_querystring != NULL);
		if(add_qs && strstr(acontrol, rtsp_querystring) != NULL)
			add_qs = FALSE;
		if(strstr(acontrol, (strlen(abase) > 0 ? abase : rtsp_url)) == acontrol) {
			/* The control attribute already contains the whole URL? */
			g_snprintf(uri, sizeof(uri), "%s%s%s", acontrol,
				add_qs ? "?" : "", add_qs ? rtsp_querystring : "");
		} else {
			/* Append the control attribute to the URL */
			g_snprintf(uri, sizeof(uri), "%s/%s%s%s", (strlen(abase) > 0 ? abase : rtsp_url),
				acontrol, add_qs ? "?" : "", add_qs ? rtsp_querystring : "");
		}
		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 (%s)\n",
				curl_easy_strerror(res), curl_errbuf);
			g_strfreev(parts);
			curl_easy_cleanup(curl);
			g_free(curl_errbuf);
			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;
		}
		res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
		if(code != 200) {
			JANUS_LOG(LOG_ERR, "Couldn't SETUP, got error code: %ld\n", code);
			g_strfreev(parts);
			curl_easy_cleanup(curl);
			g_free(curl_errbuf);
			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);
		/* Parse the RTSP message: we may need Transport and Session */
		gboolean success = TRUE;
		gchar **parts = g_strsplit(curldata->buffer, "\n", -1);
		if(parts) {
			int index = 0;
			char *line = NULL, *cr = NULL;
			while(success && (line = parts[index]) != NULL) {
				cr = strchr(line, '\r');
				if(cr != NULL)
					*cr = '\0';
				if(*line == '\0') {
					if(cr != NULL)
						*cr = '\r';
					index++;
					continue;
				}
				if(strnlen(line, 3) < 3) {
					JANUS_LOG(LOG_ERR, "Invalid RTSP line (%zu bytes): %s\n", strlen(line), line);
					success = FALSE;
					break;
				}
				/* Check if this is a Transport or Session header, and if so parse it */
				gboolean is_transport = (strstr(line, "Transport:") == line || strstr(line, "transport:") == line);
				gboolean is_session = (strstr(line, "Session:") == line || strstr(line, "session:") == line);
				if(is_transport || is_session) {
					/* There is, iterate on all params */
					char *p = line, param[100], *pi = NULL;
					int read = 0;
					gboolean first = TRUE;
					while(sscanf(p, "%99[^;]%n", param, &read) == 1) {
						if(first) {
							/* Skip */
							first = FALSE;
						} else {
							pi = param;
							while(*pi == ' ')
								pi++;
							char name[50], value[50];
							if(sscanf(pi, "%49[a-zA-Z_0-9]=%49s", name, value) == 2) {
								if(is_transport) {
									if(!strcasecmp(name, "ssrc")) {
										/* Take note of the audio SSRC */
										audio_ssrc = strtol(value, NULL, 16);
										JANUS_LOG(LOG_VERB, "  -- SSRC (audio): %"SCNu32"\n", audio_ssrc);
									} else if(!strcasecmp(name, "source")) {
										/* If we got an address via c-line, replace it */
										g_snprintf(ahost, sizeof(ahost), "%s", value);
										JANUS_LOG(LOG_VERB, "  -- Source (audio): %s\n", ahost);
									} else if(!strcasecmp(name, "server_port")) {
										/* Take note of the server port */
										char *dash = NULL;
										asport = strtol(value, &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);
									}
								} else if(is_session) {
									if(!strcasecmp(name, "timeout")) {
										/* Take note of the timeout, for keep-alive */
										source->ka_timeout = janus_streaming_min_if(source->session_timeout, (gint64)atoi(value) / 2 * G_USEC_PER_SEC);
										JANUS_LOG(LOG_VERB, "  -- RTSP session timeout (audio): %"SCNi64" ms\n", source->ka_timeout / 1000);
									}
								}
							}
						}
						/* Move to the next param */
						p += read;
						if(*p != ';')
							break;
						while(*p == ';')
							p++;
					}
				}
				if(cr != NULL)
					*cr = '\r';
				index++;
			}
			if(cr != NULL)
				*cr = '\r';
			g_strfreev(parts);
		}
		/* If we don't have a host yet (no c-line, no source in Transport), use the server address */
		if(strlen(ahost) == 0 || !strcmp(ahost, "0.0.0.0")) {
			if(strlen(vhost) > 0 && strcmp(vhost, "0.0.0.0")) {
				JANUS_LOG(LOG_WARN, "No c-line or source for RTSP audio stream, copying the video address (%s)\n", vhost);
				g_snprintf(ahost, sizeof(ahost), "%s", vhost);
			} else {
#ifdef HAVE_LIBCURL
#if CURL_AT_LEAST_VERSION(7, 62, 0)
				JANUS_LOG(LOG_WARN, "No c-line or source for RTSP audio stream, resolving server address...\n");
				CURLU *url = curl_url();
				if(url != NULL) {
					CURLUcode code = curl_url_set(url, CURLUPART_URL, source->rtsp_url, 0);
					if(code == 0) {
						char *host = NULL;
						code = curl_url_get(url, CURLUPART_HOST, &host, 0);
						if(code == 0) {
							/* Resolve the address */
							struct addrinfo *info = NULL, *start = NULL;
							janus_network_address addr;
							janus_network_address_string_buffer addr_buf;
							if(getaddrinfo(host, NULL, NULL, &info) == 0) {
								start = info;
								while(info != NULL) {
									if(janus_network_address_from_sockaddr(info->ai_addr, &addr) == 0 &&
											janus_network_address_to_string_buffer(&addr, &addr_buf) == 0) {
										/* Resolved */
										g_snprintf(ahost, sizeof(ahost), "%s",
											janus_network_address_string_from_buffer(&addr_buf));
										JANUS_LOG(LOG_VERB, "   -- %s\n", ahost);
										break;
									}
									info = info->ai_next;
								}
							}
							if(start)
								freeaddrinfo(start);
							curl_free(host);
						}
					}
					curl_url_cleanup(url);
				}
#endif
#endif
			}
		}
		if(strlen(ahost) == 0 || !strcmp(ahost, "0.0.0.0")) {
			/* Still nothing... */
			JANUS_LOG(LOG_WARN, "No host address for the RTSP audio stream, no latching will be performed\n");
		}
	}
	g_strfreev(parts);

	/* Do we have media streams already? */
	if(source->media == NULL) {
		if(doaudio) {
			janus_streaming_rtp_source_stream *stream = g_malloc0(sizeof(janus_streaming_rtp_source_stream));
			stream->mindex = g_list_length(source->media);
			stream->type = JANUS_STREAMING_MEDIA_AUDIO;
			stream->mid = g_strdup("a");
			stream->label = g_strdup("RTSP audio");
			source->media = g_list_append(source->media, stream);
			janus_refcount_init(&stream->ref, janus_streaming_rtp_source_stream_free);
		}
		if(dovideo) {
			janus_streaming_rtp_source_stream *stream = g_malloc0(sizeof(janus_streaming_rtp_source_stream));
			stream->mindex = g_list_length(source->media);
			stream->type = JANUS_STREAMING_MEDIA_VIDEO;
			stream->mid = g_strdup("v");
			stream->label = g_strdup("RTSP video");
			source->media = g_list_append(source->media, stream);
			janus_refcount_init(&stream->ref, janus_streaming_rtp_source_stream_free);
		}
	}

	/* Update the source (but check if rtpmap/fmtp need to be overridden) */
	GList *temp = source->media;
	while(temp) {
		janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
		if(stream->type == JANUS_STREAMING_MEDIA_AUDIO) {
			stream->codecs.pt = source->rtsp_acodecs.pt != -1 ? source->rtsp_acodecs.pt : apt;
			if(stream->codecs.audio_codec == JANUS_AUDIOCODEC_NONE)
				stream->codecs.audio_codec = audio_codec;
			g_free(stream->codecs.fmtp);
			stream->codecs.fmtp = source->rtsp_acodecs.fmtp ? g_strdup(source->rtsp_acodecs.fmtp) : g_strdup(afmtp);
			g_free(source->rtsp_ahost);
			source->rtsp_ahost = asport > 0 ? g_strdup(ahost) : NULL;
			stream->remote_port = asport;
			stream->remote_rtcp_port = asport_rtcp;
			/* Map this stream by all its file descriptors */
			stream->fd[0] = audio_fds.fd;
			stream->fd[1] = -1;
			stream->fd[2] = -1;
			stream->rtcp_fd = audio_fds.rtcp_fd;
			if(stream->fd[0] != -1)
				g_hash_table_insert(source->media_byfd, GINT_TO_POINTER(stream->fd[0]), stream);
			if(stream->rtcp_fd != -1)
				g_hash_table_insert(source->media_byfd, GINT_TO_POINTER(stream->rtcp_fd), stream);
		}
		if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
			stream->codecs.video_codec = video_codec;
			stream->codecs.pt = source->rtsp_vcodecs.pt != -1 ? source->rtsp_vcodecs.pt : vpt;
			if(stream->codecs.video_codec == JANUS_VIDEOCODEC_NONE)
				stream->codecs.video_codec = video_codec;
			g_free(stream->codecs.fmtp);
			stream->codecs.fmtp = source->rtsp_vcodecs.fmtp ? g_strdup(source->rtsp_vcodecs.fmtp) : g_strdup(vfmtp);
			g_free(stream->h264_spspps);
			stream->h264_spspps = sps;
			sps = NULL;
			stream->h264_spspps_len = spslen;
			spslen = 0;
			g_free(source->rtsp_vhost);
			source->rtsp_vhost = vsport > 0 ? g_strdup(vhost) : NULL;
			stream->remote_port = vsport;
			stream->remote_rtcp_port = vsport_rtcp;
			/* Map this stream by all its file descriptors */
			stream->fd[0] = video_fds.fd;
			stream->fd[1] = -1;
			stream->fd[2] = -1;
			stream->rtcp_fd = video_fds.rtcp_fd;
			if(stream->fd[0] != -1)
				g_hash_table_insert(source->media_byfd, GINT_TO_POINTER(stream->fd[0]), stream);
			if(stream->rtcp_fd != -1)
				g_hash_table_insert(source->media_byfd, GINT_TO_POINTER(stream->rtcp_fd), stream);
			if(source->bufferkf_ms > 0 || source->bufferkf_bytes > 0) {
				stream->keyframe.enabled = TRUE;
				stream->keyframe.bufferkf_ms = source->bufferkf_ms;
				stream->keyframe.bufferkf_bytes = source->bufferkf_bytes;
				stream->keyframe.latest_keyframe = NULL;
				stream->keyframe.kf_ssrc = 0;
				stream->keyframe.kf_ts = 0;
				stream->keyframe.kf_bytes = 0;
				stream->keyframe.kf_start = 0;
				stream->keyframe.first_ts = FALSE;
				janus_mutex_init(&stream->keyframe.mutex);
			}
		}
		temp = temp->next;
	}
	source->curl = curl;
	source->curl_errbuf = curl_errbuf;
	source->curldata = curldata;
	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 = { 0 };
		struct sockaddr_in6 remote6 = { 0 };
		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_in6 remote = { 0 };
	GList *temp = source->media;
	while(temp) {
		janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
		if(stream->remote_port > 0 && stream->fd[0] >= 0) {
			char *host = (stream->type == JANUS_STREAMING_MEDIA_AUDIO) ? source->rtsp_ahost : source->rtsp_vhost;
			JANUS_LOG(LOG_VERB, "RTSP %s latching: %s:%d\n",
				janus_streaming_media_str(stream->type), host, stream->remote_port);
			janus_streaming_rtsp_latch(stream->fd[0], host, stream->remote_port, (struct sockaddr *)&remote);
			if(stream->remote_rtcp_port > 0 && stream->rtcp_fd >= 0) {
				JANUS_LOG(LOG_VERB, "  -- RTCP: %s:%d\n", host, stream->remote_rtcp_port);
				janus_streaming_rtsp_latch(stream->rtcp_fd, host,
					stream->remote_rtcp_port, (struct sockaddr *)&stream->rtcp_addr);
			}
		}
		temp = temp->next;
	}
	/* 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_stream_uri ? source->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 (%s)\n",
			curl_easy_strerror(res), source->curl_errbuf);
		janus_mutex_unlock(&source->rtsp_mutex);
		return -1;
	}
	long code = 0;
	res = curl_easy_getinfo(source->curl, CURLINFO_RESPONSE_CODE, &code);
	if(code != 200) {
		JANUS_LOG(LOG_ERR, "Couldn't PLAY, got error code: %ld\n", code);
		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 *id_str, char *name, char *desc, char *metadata,
		char *url, char *username, char *password,
		gboolean quirk, gboolean notify_changes,
		gboolean doaudio, int apt, char *acodec, char *afmtp,
		gboolean dovideo, int vpt, char *vcodec, char *vfmtp,
		uint16_t bufferkf_ms, uint32_t bufferkf_bytes,
		const janus_network_address *iface, int threads,
		gint64 reconnect_delay, gint64 session_timeout, int rtsp_timeout, int rtsp_conn_timeout,
		gboolean error_on_failure) {
	char id_num[30];
	if(!string_ids) {
		g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id);
		id_str = id_num;
	}
	if(url == NULL) {
		JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream, missing url...\n");
		return NULL;
	}
	if(reconnect_delay < 0) {
		JANUS_LOG(LOG_ERR, "rtsp_reconnect_delay can't be smaller than zero.\n");
		return NULL;
	}
	if(session_timeout < 0) {
		JANUS_LOG(LOG_ERR, "rtsp_session_timeout can't be smaller than zero.\n");
		return NULL;
	}
	if(rtsp_timeout < 0) {
		JANUS_LOG(LOG_ERR, "rtsp_timeout can't be smaller than zero.\n");
		return NULL;
	}
	if(rtsp_conn_timeout < 0) {
		JANUS_LOG(LOG_ERR, "rtsp_conn_timeout can't be smaller than zero.\n");
		return NULL;
	}

	JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled");

	/* Create an RTP source for the media we'll get */
	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, "%s", id_str);
	} else if(name[0] == '0' || 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->id_str = g_strdup(id_str);
	live_rtsp->name = sourcename;
	live_rtsp->description = description;
	live_rtsp->metadata = (metadata ? g_strdup(metadata) : NULL);
	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->rtsp_stream_uri = NULL;
	live_rtsp_source->rtsp_quirk = quirk;
	live_rtsp_source->rtsp_notify_changes = notify_changes;
	live_rtsp_source->media = NULL;		/* We'll prepare this later */
	live_rtsp_source->media_byid = g_hash_table_new(NULL, NULL);
	live_rtsp_source->media_byfd = g_hash_table_new(NULL, NULL);
	live_rtsp_source->pipefd[0] = -1;
	live_rtsp_source->pipefd[1] = -1;
	pipe(live_rtsp_source->pipefd);
	live_rtsp_source->bufferkf_ms = bufferkf_ms;
	live_rtsp_source->bufferkf_bytes = bufferkf_bytes;
	live_rtsp_source->ka_timeout = session_timeout;
	live_rtsp_source->reconnect_delay = reconnect_delay;
	live_rtsp_source->session_timeout = session_timeout;
	live_rtsp_source->rtsp_timeout = rtsp_timeout;
	live_rtsp_source->rtsp_conn_timeout = rtsp_conn_timeout;
	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 codec and/or fmtp for audio and/or video */
	live_rtsp_source->rtsp_acodecs.pt = doaudio ? apt : -1;
	live_rtsp_source->rtsp_acodecs.audio_codec = JANUS_AUDIOCODEC_NONE;
	if(doaudio && acodec)
		live_rtsp_source->rtsp_acodecs.audio_codec = janus_audiocodec_from_name(acodec);
	live_rtsp_source->rtsp_acodecs.fmtp = doaudio ? (afmtp ? g_strdup(afmtp) : NULL) : NULL;
	live_rtsp_source->rtsp_vcodecs.pt = dovideo ? vpt : -1;
	live_rtsp_source->rtsp_acodecs.video_codec = JANUS_VIDEOCODEC_NONE;
	if(dovideo && vcodec)
		live_rtsp_source->rtsp_acodecs.video_codec = janus_videocodec_from_name(vcodec);
	live_rtsp_source->rtsp_vcodecs.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_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_refcount_decrease(&live_rtsp->ref);
			return NULL;
		}
	}
	/* 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_rtsp;
			helper->queued_packets = g_async_queue_new_full((GDestroyNotify)janus_streaming_rtp_relay_packet_free);
			janus_mutex_init(&helper->mutex);
			janus_refcount_init(&helper->ref, janus_streaming_helper_free);
			live_rtsp->helper_threads++;
			/* Spawn a thread and add references */
			g_snprintf(tname, sizeof(tname), "help %u-%"SCNu64, helper->id, live_rtsp->id);
			janus_refcount_increase(&live_rtsp->ref);
			janus_refcount_increase(&helper->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 : "??");
				g_error_free(error);
				janus_refcount_decrease(&live_rtsp->ref);	/* This is for the helper thread */
				g_async_queue_unref(helper->queued_packets);
				janus_refcount_decrease(&helper->ref);
				/* This extra unref is for the init */
				janus_refcount_decrease(&helper->ref);
				janus_refcount_decrease(&live_rtsp->ref);
				return NULL;
			}
			janus_refcount_increase(&helper->ref);
			live_rtsp->threads = g_list_append(live_rtsp->threads, helper);
		}
	}
	/* Finally, start the thread that will receive the media packets */
	g_snprintf(tname, sizeof(tname), "mp %s", live_rtsp->id_str);
	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 : "??");
		g_error_free(error);
		janus_refcount_decrease(&live_rtsp->ref);	/* This is for the failed thread */
		janus_refcount_decrease(&live_rtsp->ref);
		return NULL;
	}
	janus_mutex_lock(&mountpoints_mutex);
	g_hash_table_insert(mountpoints,
		string_ids ? (gpointer)g_strdup(live_rtsp->id_str) : (gpointer)janus_uint64_dup(live_rtsp->id),
		live_rtsp);
	g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)live_rtsp->id_str : (gpointer)&live_rtsp->id);
	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 *id_str, char *name, char *desc, char *metadata,
		char *url, char *username, char *password,
		gboolean quirk, gboolean notify_changes,
		gboolean doaudio, int apt, char *audiocodec, char *audiofmtp,
		gboolean dovideo, int vpt, char *videocodec, char *videofmtp,
		uint16_t bufferkf_ms, uint32_t bufferkf_bytes,
		const janus_network_address *iface, int threads,
		gint64 reconnect_delay, gint64 session_timeout, int rtsp_timeout, int rtsp_conn_timeout,
		gboolean error_on_failure) {
	JANUS_LOG(LOG_ERR, "RTSP need libcurl\n");
	return NULL;
}
#endif

/* 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;
	}
	char *name = g_strdup(mountpoint->name ? mountpoint->name : "??");
	JANUS_LOG(LOG_VERB, "[%s] Streaming audio file: %s\n", name, source->filename);

#ifdef HAVE_LIBOGG
	/* Make sure that, if this is an .opus file, we can open it */
	janus_streaming_opus_context opusctx = { 0 };
	if(source->opus) {
		opusctx.name = name;
		opusctx.filename = source->filename;
		opusctx.file = audio;
		if(janus_streaming_opus_context_init(&opusctx) < 0) {
			g_free(name);
			fclose(audio);
			janus_refcount_decrease(&session->ref);
			janus_refcount_decrease(&mountpoint->ref);
			g_thread_unref(g_thread_self());
			return NULL;
		}
	}
#endif

	/* Buffer */
	char buf[1500];
	memset(buf, 0, sizeof(buf));
	/* Set up RTP */
	guint16 seq = 1;
	guint32 ts = 0;
	janus_rtp_header *header = (janus_rtp_header *)buf;
	header->version = 2;
	header->markerbit = 1;
	header->type = source->codecs.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;
#ifdef HAVE_LIBOGG
	const gint plen = (sizeof(buf)-RTP_HEADER_SIZE);
#endif
	janus_streaming_rtp_relay_packet packet;
	while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&mountpoint->destroyed) &&
			!g_atomic_int_get(&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(!g_atomic_int_get(&session->started) || g_atomic_int_get(&session->paused) || !mountpoint->enabled)
			continue;
		if(source->opus) {
#ifdef HAVE_LIBOGG
			/* Get the next frame from the Opus file */
			read = janus_streaming_opus_context_read(&opusctx, buf + RTP_HEADER_SIZE, plen);
#endif
		} else {
			/* 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;
		/* Relay to the listener */
		packet.mindex = -1;
		packet.data = header;
		packet.length = RTP_HEADER_SIZE + read;
		packet.is_rtp = TRUE;
		packet.is_video = FALSE;
		packet.is_kfburst = FALSE;
		/* Backup the actual payload type, timestamp and sequence number */
		packet.ptype = packet.data->type;
		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 += (source->opus ? 960 : 160);
		header->timestamp = htonl(ts);
		header->markerbit = 0;
	}
	JANUS_LOG(LOG_VERB, "[%s] Leaving filesource (ondemand) thread\n", name);
#ifdef HAVE_LIBOGG
	if(source->opus)
		janus_streaming_opus_context_cleanup(&opusctx);
#endif
	g_free(name);
	fclose(audio);
	janus_refcount_decrease(&session->ref);
	janus_refcount_decrease(&mountpoint->ref);
	g_thread_unref(g_thread_self());
	return NULL;
}

/* 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;
	}
	char *name = g_strdup(mountpoint->name ? mountpoint->name : "??");
	JANUS_LOG(LOG_VERB, "[%s] Streaming audio file: %s\n", mountpoint->name, source->filename);

#ifdef HAVE_LIBOGG
	/* Make sure that, if this is an .opus file, we can open it */
	janus_streaming_opus_context opusctx = { 0 };
	if(source->opus) {
		opusctx.name = name;
		opusctx.filename = source->filename;
		opusctx.file = audio;
		if(janus_streaming_opus_context_init(&opusctx) < 0) {
			g_free(name);
			fclose(audio);
			janus_refcount_decrease(&mountpoint->ref);
			g_thread_unref(g_thread_self());
			return NULL;
		}
	}
#endif

	/* Buffer */
	char buf[1500];
	memset(buf, 0, sizeof(buf));
	/* Set up RTP */
	guint16 seq = 1;
	guint32 ts = 0;
	janus_rtp_header *header = (janus_rtp_header *)buf;
	header->version = 2;
	header->markerbit = 1;
	header->type = source->codecs.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;
#ifdef HAVE_LIBOGG
	const gint plen = (sizeof(buf)-RTP_HEADER_SIZE);
#endif
	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;
		if(source->opus) {
#ifdef HAVE_LIBOGG
			/* Get the next frame from the Opus file */
			read = janus_streaming_opus_context_read(&opusctx, buf + RTP_HEADER_SIZE, plen);
#endif
		} else {
			/* 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;
		/* Relay on all sessions */
		packet.mindex = -1;
		packet.data = header;
		packet.length = RTP_HEADER_SIZE + read;
		packet.is_rtp = TRUE;
		packet.is_video = FALSE;
		packet.is_kfburst = FALSE;
		/* Backup the actual payload type, timestamp and sequence number */
		packet.ptype = packet.data->type;
		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 += (source->opus ? 960 : 160);
		header->timestamp = htonl(ts);
		header->markerbit = 0;
	}
	JANUS_LOG(LOG_VERB, "[%s] Leaving filesource (live) thread\n", name);
#ifdef HAVE_LIBOGG
	if(source->opus)
		janus_streaming_opus_context_cleanup(&opusctx);
#endif
	g_free(name);
	fclose(audio);
	janus_refcount_decrease(&mountpoint->ref);
	return NULL;
}

/* Helper method to buffer keyframes + deltas, if needed (and if allowed) */
static void janus_streaming_buffer_keyframe_data(janus_streaming_rtp_source_stream *stream, char *buffer, int bytes) {
	if(!stream || !stream->keyframe.enabled || !buffer || bytes < 12)
		return;
	/* Check if this exceeds ms and/or bytes (the keyframe itself is never impacted) */
	if(!stream->keyframe.first_ts && stream->keyframe.bufferkf_ms > 0) {
		/* TODO Check if this exceeds the duration limit */
		int64_t now = janus_get_monotonic_time() / 1000;
		if((now - stream->keyframe.kf_start) > (int64_t)stream->keyframe.bufferkf_ms) {
			JANUS_LOG(LOG_WARN, "[kf] Not buffering keyframe data (exceeds ms limit)\n");
			return;
		}
	}
	if(!stream->keyframe.first_ts && stream->keyframe.bufferkf_bytes > 0) {
		/* Check if this exceeds the bytes limit */
		if((stream->keyframe.kf_bytes + bytes) > stream->keyframe.bufferkf_bytes) {
			JANUS_LOG(LOG_WARN, "[kf] Not buffering keyframe data (exceeds bytes limit)\n");
			return;
		}
	}
	janus_rtp_header *rtp = (janus_rtp_header *)buffer;
	janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
	pkt->mindex = stream->mindex;
	pkt->data = g_malloc(bytes);
	memcpy(pkt->data, buffer, bytes);
	pkt->data->ssrc = stream->keyframe.kf_ssrc;
	pkt->data->type = stream->codecs.pt;
	pkt->is_rtp = TRUE;
	pkt->is_video = TRUE;
	pkt->is_kfburst = TRUE;
	pkt->length = bytes;
	pkt->ptype = rtp->type;
	pkt->timestamp = ntohl(rtp->timestamp);
	pkt->seq_number = ntohs(rtp->seq_number);
	stream->keyframe.latest_keyframe = g_list_prepend(stream->keyframe.latest_keyframe, pkt);
	stream->keyframe.kf_bytes += bytes;
}

/* 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_LOG(LOG_ERR, "[%s] Not an RTP source mountpoint!\n", mountpoint->name);
		janus_refcount_decrease(&mountpoint->ref);
		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;
	}

	/* Check how many file descriptors we'll need to monitor */
	int num = 0;
	janus_streaming_rtp_source_stream *stream = NULL;
	GList *temp = source->media;
	while(temp) {
		stream = (janus_streaming_rtp_source_stream *)temp->data;
		if(stream->fd[0] != -1)
			num++;
		if(stream->fd[1] != -1)
			num++;
		if(stream->fd[2] != -1)
			num++;
		if(stream->rtcp_fd != -1)
			num++;
		temp = temp->next;
	}
	num++;	/* There's the pipe too */
	/* Keep track of how many sockets we're monitoring, as with RTSP that may change later */
	int numtot = num;

	/* Add a reference to the helper threads, if needed */
	if(mountpoint->helper_threads > 0) {
		GList *l = mountpoint->threads;
		while(l) {
			janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
			janus_refcount_increase(&ht->ref);
			l = l->next;
		}
	}

	char *name = g_strdup(mountpoint->name ? mountpoint->name : "??");
	/* Needed to fix seq and ts */
	uint32_t ssrc = 0;
	/* File descriptors */
	socklen_t addrlen;
	struct sockaddr_storage remote;
	int resfd = 0, bytes = 0;
	struct pollfd *fds = g_malloc(numtot * sizeof(struct pollfd));
	char buffer[1500];
	memset(buffer, 0, 1500);
	/* We'll have a dynamic number of streams */
#ifdef HAVE_LIBCURL
	/* In case this is an RTSP restreamer, we may have to send keep-alive from time to time */
	gint64 now = janus_get_monotonic_time(), before = now, ka_timeout = 0;
	if(source->rtsp) {
		source->reconnect_timer = now;
		ka_timeout = source->ka_timeout;
	}
	gboolean connected = TRUE;
#endif
	/* Loop */
	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 > source->reconnect_delay)) {
				/*  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);
				temp = source->media;
				while(temp) {
					stream = (janus_streaming_rtp_source_stream *)temp->data;
					if(stream->fd[0] > -1) {
						g_hash_table_remove(source->media_byfd, GINT_TO_POINTER(stream->fd[0]));
						close(stream->fd[0]);
					}
					stream->fd[0] = -1;
					if(stream->fd[1] > -1) {
						g_hash_table_remove(source->media_byfd, GINT_TO_POINTER(stream->fd[1]));
						close(stream->fd[1]);
					}
					stream->fd[1] = -1;
					if(stream->fd[2] > -1) {
						g_hash_table_remove(source->media_byfd, GINT_TO_POINTER(stream->fd[2]));
						close(stream->fd[2]);
					}
					stream->fd[2] = -1;
					if(stream->rtcp_fd > -1) {
						g_hash_table_remove(source->media_byfd, GINT_TO_POINTER(stream->rtcp_fd));
						close(stream->rtcp_fd);
					}
					stream->rtcp_fd = -1;
					temp = temp->next;
				}
				/* Let's clean up the source first */
				curl_easy_cleanup(source->curl);
				source->curl = NULL;
				g_free(source->curl_errbuf);
				source->curl_errbuf = NULL;
				if(source->curldata)
					g_free(source->curldata->buffer);
				g_free(source->curldata);
				source->curldata = NULL;
				if(g_atomic_int_get(&mountpoint->destroyed))
					break;
				if(connected) {
					/* Notify users and/or event handlers about this disconnection */
					if(notify_events && gateway->events_is_enabled()) {
						json_t *info = json_object();
						json_object_set_new(info, "event", json_string("rtsp-disconnected"));
						json_object_set_new(info, "id", string_ids ? json_string(mountpoint->id_str) : json_integer(mountpoint->id));
						gateway->notify_event(&janus_streaming_plugin, NULL, info);
					}
					if(source->rtsp_notify_changes) {
						json_t *info = json_object();
						json_object_set_new(info, "event", json_string("rtsp-disconnected"));
						json_object_set_new(info, "id", string_ids ? json_string(mountpoint->id_str) : json_integer(mountpoint->id));
						janus_mutex_lock(&mountpoint->mutex);
						janus_streaming_notify_subscribers(mountpoint, info);
						janus_mutex_unlock(&mountpoint->mutex);
						json_decref(info);
					}
				}
				/* Now let's try to reconnect */
				source->reconnect_timer = now;
				connected = FALSE;
				source->reconnecting = TRUE;
				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 */
						JANUS_LOG(LOG_INFO, "[%s] Reconnected to the RTSP server, streaming again\n", name);
						ka_timeout = source->ka_timeout;
						connected = TRUE;
						/* Check if the number of sockets to monitor changed */
						num = 0;
						temp = source->media;
						while(temp) {
							stream = (janus_streaming_rtp_source_stream *)temp->data;
							if(stream->fd[0] != -1)
								num++;
							if(stream->fd[1] != -1)
								num++;
							if(stream->fd[2] != -1)
								num++;
							if(stream->rtcp_fd != -1)
								num++;
							temp = temp->next;
						}
						num++;	/* There's the pipe too */
						if(num > numtot) {
							/* Reallocate the poll list */
							numtot = num;
							fds = g_realloc(fds, numtot * sizeof(struct pollfd));
						}
						/* Notify users and/or event handlers about this reconnection */
						if(notify_events && gateway->events_is_enabled()) {
							json_t *info = json_object();
							json_object_set_new(info, "event", json_string("rtsp-reconnected"));
							json_object_set_new(info, "id", string_ids ? json_string(mountpoint->id_str) : json_integer(mountpoint->id));
							gateway->notify_event(&janus_streaming_plugin, NULL, info);
						}
						if(source->rtsp_notify_changes) {
							json_t *info = json_object();
							json_object_set_new(info, "event", json_string("rtsp-reconnected"));
							json_object_set_new(info, "id", string_ids ? json_string(mountpoint->id_str) : json_integer(mountpoint->id));
							janus_mutex_lock(&mountpoint->mutex);
							janus_streaming_notify_subscribers(mountpoint, info);
							janus_mutex_unlock(&mountpoint->mutex);
							json_decref(info);
						}
					}
				}
				source->reconnect_timer = janus_get_monotonic_time();
				source->reconnecting = FALSE;
				continue;
			}
		}
		if(source->reconnecting || !connected) {
			/* No socket, we may be in the process of reconnecting, or waiting to reconnect */
			g_usleep(source->reconnect_delay);
			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 (%s)\n",
						name, curl_easy_strerror(resfd), source->curl_errbuf);
				}
				janus_mutex_unlock(&source->rtsp_mutex);
			}
		}
#endif
		/* Prepare poll */
		num = 0;
		temp = source->media;
		while(temp) {
			janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
			if(stream->fd[0] != -1) {
				fds[num].fd = stream->fd[0];
				fds[num].events = POLLIN;
				fds[num].revents = 0;
				num++;
			}
			if(stream->fd[1] != -1) {
				fds[num].fd = stream->fd[1];
				fds[num].events = POLLIN;
				fds[num].revents = 0;
				num++;
			}
			if(stream->fd[2] != -1) {
				fds[num].fd = stream->fd[2];
				fds[num].events = POLLIN;
				fds[num].revents = 0;
				num++;
			}
			if(stream->rtcp_fd != -1) {
				fds[num].fd = stream->rtcp_fd;
				fds[num].events = POLLIN;
				fds[num].revents = 0;
				num++;
			}
			/* Any PLI and/or REMB we should send back to the source? */
			if(stream->type == JANUS_STREAMING_MEDIA_VIDEO) {
				if(g_atomic_int_get(&stream->need_pli))
					janus_streaming_rtcp_pli_send(stream);
				if(stream->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, stream);
				}
			}
			temp = temp->next;
		}
		if(source->pipefd[0] != -1) {
			fds[num].fd = source->pipefd[0];
			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, g_strerror(errno));
				continue;
			}
			JANUS_LOG(LOG_ERR, "[%s] Error polling... %d (%s)\n", name, errno, g_strerror(errno));
			mountpoint->enabled = FALSE;
			janus_mutex_lock(&source->rec_mutex);
			GList *temp = source->media;
			while(temp) {
				janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
				janus_recorder_close(stream->rc);
				JANUS_LOG(LOG_INFO, "[%s] Closed %s recording %s (%s)\n", mountpoint->name,
					janus_streaming_media_str(stream->type), stream->rc->filename, stream->mid);
				janus_recorder *tmp = stream->rc;
				stream->rc = NULL;
				janus_recorder_destroy(tmp);
				break;
			}
			janus_mutex_unlock(&source->rec_mutex);
			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, g_strerror(errno));
				mountpoint->enabled = FALSE;
				janus_mutex_lock(&source->rec_mutex);
				GList *temp = source->media;
				while(temp) {
					janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
					janus_recorder_close(stream->rc);
					JANUS_LOG(LOG_INFO, "[%s] Closed %s recording %s (%s)\n", mountpoint->name,
						janus_streaming_media_str(stream->type), stream->rc->filename, stream->mid);
					janus_recorder *tmp = stream->rc;
					stream->rc = NULL;
					janus_recorder_destroy(tmp);
					break;
				}
				janus_mutex_unlock(&source->rec_mutex);
				break;
			} else if(fds[i].revents & POLLIN) {
				/* Got an RTP or data packet */
				if(fds[i].fd == source->pipefd[0]) {
					/* We're done here */
					int code = 0;
					bytes = read(fds[i].fd, &code, sizeof(int));
					JANUS_LOG(LOG_VERB, "[%s] Interrupting mountpoint\n", mountpoint->name);
					break;
				} else {
					/* Check which stream this file descriptor belongs to */
					stream = g_hash_table_lookup(source->media_byfd, GINT_TO_POINTER(fds[i].fd));
				}
				if(stream == NULL) {
					/* No stream..? Shouldn't happen, read the bytes and dump them */
					addrlen = sizeof(remote);
					(void)recvfrom(fds[i].fd, buffer, 1500, 0, (struct sockaddr *)&remote, &addrlen);
					continue;
				}
				if(stream->type == JANUS_STREAMING_MEDIA_AUDIO && fds[i].fd == stream->fd[0]) {
					/* 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(fds[i].fd, buffer, 1500, 0, (struct sockaddr *)&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 && stream->last_ssrc[0] && ssrc != stream->last_ssrc[0] &&
							(now-stream->last_received[0]) < (gint64)1000*source->rtp_collision) {
						JANUS_LOG(LOG_WARN, "[%s] RTP collision on audio mountpoint, dropping packet (#%d, ssrc=%"SCNu32")\n",
							name, stream->mindex, ssrc);
						continue;
					}
					stream->last_received[0] = now;
					/* Do we have a new stream? */
					if(ssrc != stream->last_ssrc[0]) {
						stream->ssrc = stream->last_ssrc[0] = ssrc;
						JANUS_LOG(LOG_INFO, "[%s] New audio stream! (#%d, ssrc=%"SCNu32")\n", name, stream->mindex, ssrc);
					}
					/* If paused, ignore this packet */
					if(!mountpoint->enabled && !stream->rc)
						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) {
							guint32 timestamp = ntohl(rtp->timestamp);
							guint16 seq = ntohs(rtp->seq_number);
							JANUS_LOG(LOG_ERR, "[%s] Audio (#%d) SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
								name, stream->mindex, janus_srtp_error_str(res), bytes, buflen, timestamp, seq);
							continue;
						}
						bytes = buflen;
					}
					/* Relay on all sessions */
					packet.mindex = stream->mindex;
					packet.data = rtp;
					packet.length = bytes;
					packet.is_rtp = TRUE;
					packet.is_video = FALSE;
					packet.is_kfburst = FALSE;
					packet.data->type = stream->codecs.pt;
					/* Is there a recorder? */
					janus_rtp_header_update(packet.data, &stream->context[0], FALSE, 0);
					if(stream->skew) {
						int ret = janus_rtp_skew_compensate_audio(packet.data, &stream->context[0], now);
						if(ret < 0) {
							JANUS_LOG(LOG_WARN, "[%s] Dropping %d packets, audio source clock is too fast (#%d, ssrc=%"SCNu32")\n",
								name, -ret, stream->mindex, ssrc);
							continue;
						} else if(ret > 0) {
							JANUS_LOG(LOG_WARN, "[%s] Jumping %d RTP sequence numbers, audio source clock is too slow (#%d, ssrc=%"SCNu32")\n",
								name, ret, stream->mindex, ssrc);
						}
					}
					if(stream->rc) {
						packet.data->ssrc = htonl((uint32_t)mountpoint->id);
						janus_recorder_save_frame(stream->rc, buffer, bytes);
					}
					if(mountpoint->enabled) {
						packet.data->ssrc = htonl(ssrc);
						/* Backup the actual payload type, timestamp and sequence number set by the restreamer, in case switching is involved */
						packet.ptype = packet.data->type;
						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(stream->type == JANUS_STREAMING_MEDIA_VIDEO && ((fds[i].fd == stream->fd[0]) ||
						(fds[i].fd == stream->fd[1]) || (fds[i].fd == stream->fd[2]))) {
					/* Got something video (RTP) */
					int index = -1;
					if(fds[i].fd == stream->fd[0])
						index = 0;
					else if(fds[i].fd == stream->fd[1])
						index = 1;
					else if(fds[i].fd == stream->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, (struct sockaddr *)&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 && stream->last_ssrc[index] && ssrc != stream->last_ssrc[index] &&
							(now-stream->last_received[index]) < (gint64)1000*source->rtp_collision) {
						JANUS_LOG(LOG_WARN, "[%s] RTP collision on video mountpoint, dropping packet (#%d, ssrc=%"SCNu32")\n",
							name, stream->mindex, ssrc);
						continue;
					}
					stream->last_received[index] = now;
					/* Do we have a new stream? */
					if(ssrc != stream->last_ssrc[index]) {
						stream->last_ssrc[index] = ssrc;
						if(index == 0)
							stream->ssrc = ssrc;
						JANUS_LOG(LOG_INFO, "[%s] New video stream! (#%d, ssrc=%"SCNu32", index %d)\n",
							name, stream->mindex, ssrc, index);
					}
					/* 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) {
							guint32 timestamp = ntohl(rtp->timestamp);
							guint16 seq = ntohs(rtp->seq_number);
							JANUS_LOG(LOG_ERR, "[%s] Video (#%d) SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
								name, stream->mindex, 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(index == 0 && stream->keyframe.enabled) {
						/* Check how we should process this packet */
						int plen = 0;
						char *payload = janus_rtp_payload(buffer, bytes, &plen);
						gboolean keyframe = janus_is_keyframe(stream->codecs.video_codec, payload, plen);
						janus_mutex_lock(&stream->keyframe.mutex);
						if(!keyframe && stream->keyframe.latest_keyframe != NULL && ntohl(rtp->timestamp) == stream->keyframe.kf_ts) {
							/* New fragment of the latest frame we received (keyframe or not),
							 * re-use the same SSRC we allocated before for this specific frame */
							JANUS_LOG(LOG_HUGE, "[kf]   -- Updating frame (ts=%"SCNu32", ssrc=%"SCNu32")\n",
								stream->keyframe.kf_ts, stream->keyframe.kf_ssrc);
							janus_streaming_buffer_keyframe_data(stream, buffer, bytes);
						} else {
							/* New frame: check if it's a delta or a keyframe. If it's a
							 * keyframe, it means we can start a new list and get rid of the
							 * previous (and now old) one, if we had one; if it's a delta,
							 * we append it to the list if it exists, and drop it if it
							 * doesn't (as it makes no sense to start from a delta) */
							if(keyframe) {
								/* This is a keyframe: remove the old list, if
								 * we had one, and start a new one from scratch */
								if(stream->keyframe.latest_keyframe != NULL)
									g_list_free_full(stream->keyframe.latest_keyframe, (GDestroyNotify)janus_streaming_rtp_relay_packet_free);
								stream->keyframe.latest_keyframe = NULL;
								stream->keyframe.kf_ssrc = janus_random_uint32();
								stream->keyframe.kf_ts = ntohl(rtp->timestamp);
								stream->keyframe.kf_bytes = 0;
								stream->keyframe.kf_start = janus_get_monotonic_time() / 1000;
								stream->keyframe.first_ts = TRUE;
								JANUS_LOG(LOG_HUGE, "[kf] New keyframe (ts=%"SCNu32", ssrc=%"SCNu32")\n",
									stream->keyframe.kf_ts, stream->keyframe.kf_ssrc);
								janus_streaming_buffer_keyframe_data(stream, buffer, bytes);
							} else if(stream->keyframe.latest_keyframe != NULL) {
								/* This is a new delta: track the timestamp, allocate
								 * a new SSRC, and add the packet to our existing list */
								stream->keyframe.kf_ssrc = janus_random_uint32();
								stream->keyframe.kf_ts = ntohl(rtp->timestamp);
								stream->keyframe.first_ts = FALSE;
								JANUS_LOG(LOG_HUGE, "[kf] New delta (ts=%"SCNu32", ssrc=%"SCNu32")\n",
									stream->keyframe.kf_ts, stream->keyframe.kf_ssrc);
								janus_streaming_buffer_keyframe_data(stream, buffer, bytes);
							} else {
								JANUS_LOG(LOG_WARN, "[kf] Dropping initial delta on empty list\n");
							}
						}
						janus_mutex_unlock(&stream->keyframe.mutex);
					}
					/* If paused, ignore this packet */
					if(!mountpoint->enabled && !stream->rc)
						continue;
					/* Relay on all sessions */
					packet.mindex = stream->mindex;
					packet.data = rtp;
					packet.length = bytes;
					packet.is_rtp = TRUE;
					packet.is_video = TRUE;
					packet.is_kfburst = FALSE;
					packet.simulcast = stream->simulcast;
					packet.substream = index;
					packet.codec = stream->codecs.video_codec;
					packet.svc = FALSE;
					if(stream->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) {
							gboolean found = FALSE;
							memset(&packet.svc_info, 0, sizeof(packet.svc_info));
							if(janus_vp9_parse_svc(payload, plen, &found, &packet.svc_info) == 0) {
								packet.svc = found;
							}
						}
					}
					packet.data->type = stream->codecs.pt;
					/* Is there a recorder? (FIXME notice we only record the first substream, if simulcasting) */
					janus_rtp_header_update(packet.data, &stream->context[index], TRUE, 0);
					if(stream->skew) {
						int ret = janus_rtp_skew_compensate_video(packet.data, &stream->context[index], now);
						if(ret < 0) {
							JANUS_LOG(LOG_WARN, "[%s] Dropping %d packets, video source clock is too fast (#%d, ssrc=%"SCNu32", index %d)\n",
								name, -ret, stream->mindex, ssrc, index);
							continue;
						} else if(ret > 0) {
							JANUS_LOG(LOG_WARN, "[%s] Jumping %d RTP sequence numbers, video source clock is too slow (#%d, ssrc=%"SCNu32", index %d)\n",
								name, ret, stream->mindex, ssrc, index);
						}
					}
					if(stream->h264_spspps) {
						int plen = 0;
						char *payload = janus_rtp_payload((char *)packet.data, bytes, &plen);
						/* We have our own SPS/PPS to send, check if we just received a keyframe */
						if(payload && janus_h264_is_i_frame(payload, plen)) {
							/* This is an I-frame: prepend an SPS/PPS packet */
							janus_rtp_header *sps_rtp = (janus_rtp_header *)stream->h264_spspps;
							sps_rtp->type = rtp->type;
							sps_rtp->seq_number = rtp->seq_number;
							rtp->seq_number = htons(ntohs(rtp->seq_number) + 1);
							stream->context[index].base_seq--;
							sps_rtp->timestamp = rtp->timestamp;
							/* Save the packet, if needed */
							sps_rtp->ssrc = htonl((uint32_t)mountpoint->id);
							janus_recorder_save_frame(stream->rc, stream->h264_spspps, stream->h264_spspps_len);
							sps_rtp->ssrc = rtp->ssrc;
							/* Relay on all sessions */
							janus_streaming_rtp_relay_packet spspkt = { 0 };
							spspkt.mindex = stream->mindex;
							spspkt.data = sps_rtp;
							spspkt.length = stream->h264_spspps_len;
							spspkt.is_rtp = TRUE;
							spspkt.is_video = TRUE;
							spspkt.is_kfburst = FALSE;
							spspkt.simulcast = FALSE;
							spspkt.codec = stream->codecs.video_codec;
							spspkt.svc = FALSE;
							spspkt.ptype = spspkt.data->type;
							spspkt.timestamp = ntohl(spspkt.data->timestamp);
							spspkt.seq_number = ntohs(spspkt.data->seq_number);
							janus_mutex_lock(&mountpoint->mutex);
							JANUS_LOG(LOG_HUGE, "[%s] Sending SPS/PPS (seq=%"SCNu16", ts=%"SCNu32")\n", name,
								ntohs(spspkt.data->seq_number), ntohl(spspkt.data->timestamp));
							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,
								&spspkt);
							janus_mutex_unlock(&mountpoint->mutex);
						}
					}
					if(index == 0 && stream->rc) {
						packet.data->ssrc = htonl((uint32_t)mountpoint->id);
						janus_recorder_save_frame(stream->rc, buffer, bytes);
					}
					if(mountpoint->enabled) {
						packet.data->ssrc = htonl(ssrc);
						/* Backup the actual payload type, timestamp and sequence number set by the restreamer, in case switching is involved */
						packet.ptype = packet.data->type;
						packet.timestamp = ntohl(packet.data->timestamp);
						packet.seq_number = ntohs(packet.data->seq_number);
						/* Take note of the simulcast SSRCs */
						if(stream->simulcast) {
							packet.ssrc[0] = stream->last_ssrc[0];
							packet.ssrc[1] = stream->last_ssrc[1];
							packet.ssrc[2] = stream->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(stream->type == JANUS_STREAMING_MEDIA_DATA && fds[i].fd == stream->fd[0]) {
					/* Got something data (text) */
					if(mountpoint->active == FALSE)
						mountpoint->active = TRUE;
					stream->last_received[0] = janus_get_monotonic_time();
#ifdef HAVE_LIBCURL
					source->reconnect_timer = janus_get_monotonic_time();
#endif
					addrlen = sizeof(remote);
					bytes = recvfrom(fds[i].fd, buffer, 1500, 0, (struct sockaddr *)&remote, &addrlen);
					if(bytes < 1) {
						/* Failed to read? */
						continue;
					}
					if(!mountpoint->enabled && !stream->rc)
						continue;
					/* Copy the data */
					char *data = g_malloc(bytes);
					memcpy(data, buffer, bytes);
					/* Relay on all sessions */
					packet.mindex = stream->mindex;
					packet.data = (janus_rtp_header *)data;
					packet.length = bytes;
					packet.is_rtp = FALSE;
					packet.is_data = TRUE;
					packet.textdata = stream->textdata;
					/* Is there a recorder? */
					janus_recorder_save_frame(stream->rc, data, bytes);
					if(mountpoint->enabled) {
						/* Are we keeping track of the last message being relayed? */
						if(stream->buffermsg) {
							janus_mutex_lock(&stream->buffermsg_mutex);
							if(stream->last_msg != NULL) {
								janus_streaming_rtp_relay_packet_free((janus_streaming_rtp_relay_packet *)stream->last_msg);
								stream->last_msg = NULL;
							}
							janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
							pkt->data = g_malloc(bytes);
							memcpy(pkt->data, data, bytes);
							pkt->mindex = stream->mindex;
							pkt->is_data = TRUE;
							pkt->textdata = stream->textdata;
							pkt->length = bytes;
							/* Store the latest message */
							stream->last_msg = pkt;
							janus_mutex_unlock(&stream->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);
					}
					g_free(packet.data);
					packet.data = NULL;
					continue;
				} else if(fds[i].fd == stream->rtcp_fd) {
					addrlen = sizeof(remote);
					bytes = recvfrom(fds[i].fd, buffer, 1500, 0, (struct sockaddr *)&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;
					}
					if(!mountpoint->enabled)
						continue;
					memcpy(&stream->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/video RTCP feedback: #%d, SSRC %"SCNu32"\n",
						name, stream->mindex, janus_rtcp_get_sender_ssrc(buffer, bytes));
					/* Relay on all sessions */
					packet.mindex = stream->mindex;
					packet.is_rtp = FALSE;
					packet.is_video = (stream->type == JANUS_STREAMING_MEDIA_VIDEO);
					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);
				}
			}
		}
	}

	/* Close the ports we bound to */
	temp = source->media;
	while(temp) {
		janus_streaming_rtp_source_stream *stream = (janus_streaming_rtp_source_stream *)temp->data;
		if(stream->fd[0] > -1)
			close(stream->fd[0]);
		stream->fd[0] = -1;
		if(stream->fd[1] > -1)
			close(stream->fd[1]);
		stream->fd[1] = -1;
		if(stream->fd[2] > -1)
			close(stream->fd[2]);
		stream->fd[2] = -1;
		if(stream->rtcp_fd > -1)
			close(stream->rtcp_fd);
		stream->rtcp_fd = -1;
		temp = temp->next;
	}
	g_free(fds);

	/* 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) {
			mountpoint->viewers = g_list_remove_all(mountpoint->viewers, session);
			viewer = g_list_first(mountpoint->viewers);
			continue;
		}
		janus_mutex_lock(&session->mutex);
		if(session->mountpoint != mountpoint) {
			mountpoint->viewers = g_list_remove_all(mountpoint->viewers, session);
			viewer = g_list_first(mountpoint->viewers);
			janus_mutex_unlock(&session->mutex);
			continue;
		}
		g_atomic_int_set(&session->stopping, 1);
		g_atomic_int_set(&session->started, 0);
		g_atomic_int_set(&session->paused, 0);
		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);
		janus_mutex_unlock(&session->mutex);
	}
	json_decref(event);
	janus_mutex_unlock(&mountpoint->mutex);

	/* Unref the helper threads */
	if(mountpoint->helper_threads > 0) {
		GList *l = mountpoint->threads;
		while(l) {
			janus_streaming_helper *ht = (janus_streaming_helper *)l->data;
			janus_refcount_decrease(&ht->ref);
			l = l->next;
		}
	}

	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) {
		return;
	}
	if(!packet->is_kfburst && (!g_atomic_int_get(&session->started) || g_atomic_int_get(&session->paused))) {
		return;
	}
	janus_streaming_session_stream *s = g_hash_table_lookup(session->streams_byid, GINT_TO_POINTER(packet->mindex));
	if(s == NULL) {
		/* No session stream for this mindex: maybe the viewer did not subscribe to it */
		return;
	}
	/* Make sure we're allowed to send packets from this stream */
	if(!s->send) {
		return;
	}
	janus_streaming_rtp_source_stream *stream = s->stream;

	if(packet->is_rtp) {
		/* Make sure there hasn't been a video source switch by checking the SSRC */
		if(packet->is_video) {
			/* 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 */
				int plen = 0;
				char *payload = janus_rtp_payload((char *)packet->data, packet->length, &plen);
				gboolean keyframe = janus_vp9_is_keyframe((const char *)payload, plen);
				gboolean override_mark_bit = FALSE, has_marker_bit = packet->data->markerbit;
				int spatial_layer = s->spatial_layer;
				gint64 now = janus_get_monotonic_time();
				if(packet->svc_info.spatial_layer >= 0 && packet->svc_info.spatial_layer <= 2)
					s->last_spatial_layer[packet->svc_info.spatial_layer] = now;
				if(s->target_spatial_layer > s->spatial_layer) {
					JANUS_LOG(LOG_HUGE, "We need to upscale spatially: (%d < %d)\n",
						s->spatial_layer, s->target_spatial_layer);
					/* We need to upscale: wait for a keyframe */
					if(keyframe) {
						int new_spatial_layer = s->target_spatial_layer;
						while(new_spatial_layer > s->spatial_layer && new_spatial_layer > 0) {
							if(now - s->last_spatial_layer[new_spatial_layer] >= 250000) {
								/* We haven't received packets from this layer for a while, try a lower layer */
								JANUS_LOG(LOG_HUGE, "Haven't received packets from layer %d for a while, trying %d instead...\n",
									new_spatial_layer, new_spatial_layer-1);
								new_spatial_layer--;
							} else {
								break;
							}
						}
						if(new_spatial_layer > s->spatial_layer) {
							JANUS_LOG(LOG_HUGE, "  -- Upscaling spatial layer: %d --> %d (need %d)\n",
								s->spatial_layer, new_spatial_layer, s->target_spatial_layer);
							s->spatial_layer = new_spatial_layer;
							spatial_layer = s->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, "mid", json_string(stream->mid));
							json_object_set_new(result, "spatial_layer", json_integer(s->spatial_layer));
							if(s->temporal_layer == -1) {
								/* We just started: initialize the temporal layer and notify that too */
								s->temporal_layer = 0;
								json_object_set_new(result, "temporal_layer", json_integer(s->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(s->target_spatial_layer < s->spatial_layer) {
					/* We need to downscale */
					JANUS_LOG(LOG_HUGE, "We need to downscale spatially: (%d > %d)\n",
						s->spatial_layer, s->target_spatial_layer);
					gboolean downscaled = FALSE;
					if(!packet->svc_info.fbit && keyframe) {
						/* Non-flexible mode: wait for a keyframe */
						downscaled = TRUE;
					} else if(packet->svc_info.fbit && packet->svc_info.ebit) {
						/* Flexible mode: check the E bit */
						downscaled = TRUE;
					}
					if(downscaled) {
						JANUS_LOG(LOG_HUGE, "  -- Downscaling spatial layer: %d --> %d\n",
							s->spatial_layer, s->target_spatial_layer);
						s->spatial_layer = s->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, "mid", json_string(stream->mid));
						json_object_set_new(result, "spatial_layer", json_integer(s->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->svc_info.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->svc_info.spatial_layer);
					s->context.base_seq++;
					return;
				} else if(packet->svc_info.ebit && spatial_layer == packet->svc_info.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;
				}
				int temporal_layer = s->temporal_layer;
				if(s->target_temporal_layer > s->temporal_layer) {
					/* We need to upscale */
					JANUS_LOG(LOG_HUGE, "We need to upscale temporally: (%d < %d)\n",
						s->temporal_layer, s->target_temporal_layer);
					if(packet->svc_info.ubit && packet->svc_info.bbit &&
							packet->svc_info.temporal_layer > s->temporal_layer &&
							packet->svc_info.temporal_layer <= s->target_temporal_layer) {
						JANUS_LOG(LOG_HUGE, "  -- Upscaling temporal layer: %d --> %d (want %d)\n",
							s->temporal_layer, packet->svc_info.temporal_layer, s->target_temporal_layer);
						s->temporal_layer = packet->svc_info.temporal_layer;
						temporal_layer = s->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, "mid", json_string(stream->mid));
						json_object_set_new(result, "temporal_layer", json_integer(s->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(s->target_temporal_layer < s->temporal_layer) {
					/* We need to downscale */
					JANUS_LOG(LOG_HUGE, "We need to downscale temporally: (%d > %d)\n",
						s->temporal_layer, s->target_temporal_layer);
					if(packet->svc_info.ebit && packet->svc_info.temporal_layer == s->target_temporal_layer) {
						JANUS_LOG(LOG_HUGE, "  -- Downscaling temporal layer: %d --> %d\n",
							s->temporal_layer, s->target_temporal_layer);
						s->temporal_layer = s->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, "mid", json_string(stream->mid));
						json_object_set_new(result, "temporal_layer", json_integer(s->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->svc_info.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->svc_info.temporal_layer);
					s->context.base_seq++;
					return;
				}
				/* 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->svc_info.spatial_layer, packet->svc_info.temporal_layer);
				/* Fix sequence number and timestamp (publisher switching may be involved) */
				janus_rtp_header_update(packet->data, &s->context, TRUE, 0);
				if(override_mark_bit && !has_marker_bit) {
					packet->data->markerbit = 1;
				}
				if(s->pt > 0)
					packet->data->type = s->pt;
				janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length };
				janus_plugin_rtp_extensions_reset(&rtp.extensions);
				if(s->min_delay > -1 && s->max_delay > -1) {
					rtp.extensions.min_delay = s->min_delay;
					rtp.extensions.max_delay = s->max_delay;
				}
				if(session->abscapturetime_src_ext_id > 0) {
					uint64_t abs_ts = 0;
					if(janus_rtp_header_extension_parse_abs_capture_time((char *)packet->data, packet->length,
							session->abscapturetime_src_ext_id, &abs_ts) == 0) {
						rtp.extensions.abs_capture_ts = abs_ts;
					}
				}
				if(gateway != NULL)
					gateway->relay_rtp(session->handle, &rtp);
				if(override_mark_bit && !has_marker_bit) {
					packet->data->markerbit = 0;
				}
				/* Restore the payload type, timestamp and sequence number to what the publisher set them to */
				packet->data->type = packet->ptype;
				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(&s->sim_context,
					(char *)packet->data, packet->length, NULL, 0,
					packet->ssrc, NULL, packet->codec, &s->context, NULL);
				if(!relay) {
					/* Did a lot of time pass before we could relay a packet? */
					gint64 now = janus_get_monotonic_time();
					if((now - s->sim_context.last_relayed) >= G_USEC_PER_SEC) {
						g_atomic_int_set(&s->sim_context.need_pli, 1);
					}
				}
				if(s->sim_context.need_pli) {
					/* Schedule a PLI */
					JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n");
					g_atomic_int_set(&stream->need_pli, 1);
				}
				/* Do we need to drop this? */
				if(!relay)
					return;
				/* Any event we should notify? */
				if(s->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, "mid", json_string(stream->mid));
					json_object_set_new(result, "substream", json_integer(s->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(s->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, "mid", json_string(stream->mid));
					json_object_set_new(result, "temporal", json_integer(s->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, &s->context, TRUE, 0);
				char vp8pd[6];
				if(packet->codec == JANUS_VIDEOCODEC_VP8 && plen >= (int)sizeof(vp8pd)) {
					/* For VP8, we save the original payload descriptor, to restore it after */
					memcpy(vp8pd, payload, sizeof(vp8pd));
					janus_vp8_simulcast_descriptor_update(payload, plen, &s->vp8_context,
						s->sim_context.changed_substream);
				}
				if(s->pt > 0)
					packet->data->type = s->pt;
				/* Send the packet */
				janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length };
				janus_plugin_rtp_extensions_reset(&rtp.extensions);
				if(s->min_delay > -1 && s->max_delay > -1) {
					rtp.extensions.min_delay = s->min_delay;
					rtp.extensions.max_delay = s->max_delay;
				}
				if(session->abscapturetime_src_ext_id > 0) {
					uint64_t abs_ts = 0;
					if(janus_rtp_header_extension_parse_abs_capture_time((char *)packet->data, packet->length,
							session->abscapturetime_src_ext_id, &abs_ts) == 0) {
						rtp.extensions.abs_capture_ts = abs_ts;
					}
				}
				if(gateway != NULL)
					gateway->relay_rtp(session->handle, &rtp);
				/* Restore the timestamp and sequence number to what the publisher set them to */
				packet->data->type = packet->ptype;
				packet->data->timestamp = htonl(packet->timestamp);
				packet->data->seq_number = htons(packet->seq_number);
				if(packet->codec == JANUS_VIDEOCODEC_VP8 && plen >= (int)sizeof(vp8pd)) {
					/* 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, &s->context, TRUE, 0);
				if(s->pt > 0)
					packet->data->type = s->pt;
				janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length };
				janus_plugin_rtp_extensions_reset(&rtp.extensions);
				if(s->min_delay > -1 && s->max_delay > -1) {
					rtp.extensions.min_delay = s->min_delay;
					rtp.extensions.max_delay = s->max_delay;
				}
				if(session->abscapturetime_src_ext_id > 0) {
					uint64_t abs_ts = 0;
					if(janus_rtp_header_extension_parse_abs_capture_time((char *)packet->data, packet->length,
							session->abscapturetime_src_ext_id, &abs_ts) == 0) {
						rtp.extensions.abs_capture_ts = abs_ts;
					}
				}
				if(gateway != NULL)
					gateway->relay_rtp(session->handle, &rtp);
				/* Restore the timestamp and sequence number to what the video source set them to */
				packet->data->type = packet->ptype;
				packet->data->timestamp = htonl(packet->timestamp);
				packet->data->seq_number = htons(packet->seq_number);
			}
		} else {
			/* Fix sequence number and timestamp (switching may be involved) */
			janus_rtp_header_update(packet->data, &s->context, FALSE, 0);
			if(s->pt > 0)
				packet->data->type = s->pt;
			janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length };
			janus_plugin_rtp_extensions_reset(&rtp.extensions);
			if(gateway != NULL)
				gateway->relay_rtp(session->handle, &rtp);
			/* Restore the timestamp and sequence number to what the video source set them to */
			packet->data->type = packet->ptype;
			packet->data->timestamp = htonl(packet->timestamp);
			packet->data->seq_number = htons(packet->seq_number);
		}
	} else {
		/* We're broadcasting a data channel message */
		if(gateway != NULL && packet->data != NULL && g_atomic_int_get(&session->dataready)) {
			janus_plugin_data data = {
				.label = NULL,
				.protocol = NULL,
				.binary = !packet->textdata,
				.buffer = (char *)packet->data,
				.length = packet->length
			};
			gateway->relay_data(session->handle, &data);
		}
	}

	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) {
		return;
	}
	if(!g_atomic_int_get(&session->started) || g_atomic_int_get(&session->paused)) {
		return;
	}
	janus_streaming_session_stream *s = g_hash_table_lookup(session->streams_byid, GINT_TO_POINTER(packet->mindex));
	if(packet->mindex != -1 && s == NULL) {
		JANUS_LOG(LOG_WARN, "No session stream for mindex %d...\n", packet->mindex);
		return;
	}

	janus_plugin_rtcp rtcp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length };
	if(gateway != NULL)
		gateway->relay_rtcp(session->handle, &rtcp);

	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) {
		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->mindex = packet->mindex;
	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_data = packet->is_data;
	copy->textdata = packet->textdata;
	copy->is_video = packet->is_video;
	copy->is_kfburst = packet->is_kfburst;
	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->svc = packet->svc;
	if(copy->svc)
		copy->svc_info = packet->svc_info;
	copy->ptype = packet->ptype;
	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) && !g_atomic_int_get(&helper->destroyed)) {
		pkt = g_async_queue_pop(helper->queued_packets);
		if(pkt == &exit_packet)
			break;
		janus_mutex_lock(&helper->mutex);
		g_list_foreach(helper->viewers,
			pkt->is_rtp || pkt->is_data ? 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_refcount_decrease(&helper->ref);
	janus_refcount_decrease(&mp->ref);
	g_thread_unref(g_thread_self());
	return NULL;
}
