diff --git a/CHANGELOG.md b/CHANGELOG.md index a2ff1440a1e..99d878b8842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. +## [v1.1.0] - 2022-10-03 + +- Added versioning to .so files [[PR-3075](https://github.com/meetecho/janus-gateway/pull/3075)] +- Allow plugins to specify msid in SDPs [[PR-2998](https://github.com/meetecho/janus-gateway/pull/2998)] +- Fixed broken RTCP timestamp on 32bit architectures [[Issue-3045](https://github.com/meetecho/janus-gateway/issues/3045)] +- Fixed problems compiling against recent versions of libwebsockets [[Issue-3039](https://github.com/meetecho/janus-gateway/issues/3039)] +- Updated deprecated DTLS functions to OpenSSL v3.0 [PR-3048](https://github.com/meetecho/janus-gateway/pull/3048)] +- Switched to SHA256 for signing self signed DTLS certificates (thanks @tgabi333!) [[PR-3069](https://github.com/meetecho/janus-gateway/pull/3069)] +- Started using strnlen to optimize performance of some strlen calls (thanks @tmatth!) [[PR-3059](https://github.com/meetecho/janus-gateway/pull/3059)] +- Added checks to avoid RTX payload type collisions [[PR-3080](https://github.com/meetecho/janus-gateway/pull/3080)] +- Added new APIs for cascading VideoRoom publishers [[PR-3014](https://github.com/meetecho/janus-gateway/pull/3014)] +- Fixed deadlock when using legacy switch in VideoRoom [[Issue-3066](https://github.com/meetecho/janus-gateway/issues/3066)] +- Fixed disabled property not being advertized to subscribers when VideoRoom publishers removed tracks +- Fixed occasional deadlock when using G.711 in the AudioBridge [[Issue-3062](https://github.com/meetecho/janus-gateway/issues/3062)] +- Added new way of capturing devices/tracks in janus.js [[PR-3003](https://github.com/meetecho/janus-gateway/pull/3003)] +- Removed call to .stop() for remote tracks in demos [[PR-3056](https://github.com/meetecho/janus-gateway/pull/3056)] +- Fixed missing message/info/transfer buttons in SIP demo page +- Fixed postprocessing compilation issue on older FFmpeg versions [[PR-3064](https://github.com/meetecho/janus-gateway/pull/3064)] +- Other smaller fixes and improvements (thanks to all who contributed pull requests and reported issues!) + ## [v1.0.4] - 2022-08-01 - Fixed problem with duplicate ptypes when codecs are added in renegotiations diff --git a/bower.json b/bower.json index e278594303c..9725cb74fa7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "janus-gateway", - "version": "1.1.0", + "version": "1.1.1", "homepage": "https://github.com/meetecho/janus-gateway", "authors": [ "Lorenzo Miniero ", diff --git a/configure.ac b/configure.ac index 74c147b9b9a..92ec54820ee 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([Janus WebRTC Server],[1.1.0],[https://github.com/meetecho/janus-gateway],[janus-gateway],[https://janus.conf.meetecho.com]) +AC_INIT([Janus WebRTC Server],[1.1.1],[https://github.com/meetecho/janus-gateway],[janus-gateway],[https://janus.conf.meetecho.com]) AC_LANG(C) AC_CONFIG_AUX_DIR([.]) AC_CONFIG_MACRO_DIR([m4]) @@ -70,10 +70,12 @@ cc*) -Wunused-but-set-variable" esac -JANUS_VERSION=1100 +JANUS_VERSION=1101 AC_SUBST(JANUS_VERSION) -JANUS_VERSION_STRING="1.1.0" +JANUS_VERSION_STRING="1.1.1" AC_SUBST(JANUS_VERSION_STRING) +JANUS_VERSION_SO="1:1:1" +AC_SUBST(JANUS_VERSION_SO) case "$host_os" in darwin*) diff --git a/docs/janus-doxygen.cfg b/docs/janus-doxygen.cfg index 65c2efbfc6b..667a9b97e33 100644 --- a/docs/janus-doxygen.cfg +++ b/docs/janus-doxygen.cfg @@ -38,7 +38,7 @@ PROJECT_NAME = "Janus (multistream)" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.1.0 +PROJECT_NUMBER = 1.1.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/html/janus.js b/html/janus.js index cf46e9bd7f2..4e2f307c679 100644 --- a/html/janus.js +++ b/html/janus.js @@ -1925,8 +1925,9 @@ function Janus(gatewayCallbacks) { Janus.log('Remote track removed:', ev); clearTimeout(trackMutedTimeoutId); // Notify the application - let transceiver = config.pc.getTransceivers().find( - t => t.receiver.track === ev.target); + let transceivers = config.pc ? config.pc.getTransceivers() : null; + let transceiver = transceivers ? transceivers.find( + t => t.receiver.track === ev.target) : null; let mid = transceiver ? transceiver.mid : ev.target.id; try { pluginHandle.onremotetrack(ev.target, mid, false); diff --git a/html/mvideoroomtest.js b/html/mvideoroomtest.js index 71e37b328d2..ab129d1a4a0 100644 --- a/html/mvideoroomtest.js +++ b/html/mvideoroomtest.js @@ -107,8 +107,8 @@ $(document).ready(function() { // This controls allows us to override the global room bitrate cap $('#bitrate').parent().parent().removeClass('hide').show(); $('#bitrate a').click(function() { - var id = $(this).attr("id"); - var bitrate = parseInt(id)*1000; + let id = $(this).attr("id"); + let bitrate = parseInt(id)*1000; if(bitrate === 0) { Janus.log("Not limiting bandwidth via REMB"); } else { @@ -125,7 +125,7 @@ $(document).ready(function() { }, onmessage: function(msg, jsep) { Janus.debug(" ::: Got a message (publisher) :::", msg); - var event = msg["videoroom"]; + let event = msg["videoroom"]; Janus.debug("Event: " + event); if(event != undefined && event != null) { if(event === "joined") { @@ -141,24 +141,28 @@ $(document).ready(function() { } // Any new feed to attach to? if(msg["publishers"]) { - var list = msg["publishers"]; + let list = msg["publishers"]; Janus.debug("Got a list of available publishers/feeds:", list); - var sources = null; - for(var f in list) { + let sources = null; + for(let f in list) { if(list[f]["dummy"]) continue; - var id = list[f]["id"]; - var display = list[f]["display"]; - var streams = list[f]["streams"]; - for(var i in streams) { - var stream = streams[i]; + let id = list[f]["id"]; + let display = list[f]["display"]; + let streams = list[f]["streams"]; + for(let i in streams) { + let stream = streams[i]; stream["id"] = id; stream["display"] = display; } + let slot = feedStreams[id] ? feedStreams[id].slot : null; + let remoteVideos = feedStreams[id] ? feedStreams[id].remoteVideos : 0; feedStreams[id] = { id: id, display: display, - streams: streams + streams: streams, + slot: slot, + remoteVideos: remoteVideos } Janus.debug(" >> [" + id + "] " + display + ":", streams); if(!sources) @@ -177,9 +181,9 @@ $(document).ready(function() { } else if(event === "event") { // Any info on our streams or a new feed to attach to? if(msg["streams"]) { - var streams = msg["streams"]; - for(var i in streams) { - var stream = streams[i]; + let streams = msg["streams"]; + for(let i in streams) { + let stream = streams[i]; stream["id"] = myid; stream["display"] = myusername; } @@ -189,24 +193,28 @@ $(document).ready(function() { streams: streams } } else if(msg["publishers"]) { - var list = msg["publishers"]; + let list = msg["publishers"]; Janus.debug("Got a list of available publishers/feeds:", list); - var sources = null; - for(var f in list) { + let sources = null; + for(let f in list) { if(list[f]["dummy"]) continue; - var id = list[f]["id"]; - var display = list[f]["display"]; - var streams = list[f]["streams"]; - for(var i in streams) { - var stream = streams[i]; + let id = list[f]["id"]; + let display = list[f]["display"]; + let streams = list[f]["streams"]; + for(let i in streams) { + let stream = streams[i]; stream["id"] = id; stream["display"] = display; } + let slot = feedStreams[id] ? feedStreams[id].slot : null; + let remoteVideos = feedStreams[id] ? feedStreams[id].remoteVideos : 0; feedStreams[id] = { id: id, display: display, - streams: streams + streams: streams, + slot: slot, + remoteVideos: remoteVideos } Janus.debug(" >> [" + id + "] " + display + ":", streams); if(!sources) @@ -217,12 +225,12 @@ $(document).ready(function() { subscribeTo(sources); } else if(msg["leaving"]) { // One of the publishers has gone away? - var leaving = msg["leaving"]; + let leaving = msg["leaving"]; Janus.log("Publisher left: " + leaving); unsubscribeFrom(leaving); } else if(msg["unpublished"]) { // One of the publishers has unpublished? - var unpublished = msg["unpublished"]; + let unpublished = msg["unpublished"]; Janus.log("Publisher left: " + unpublished); if(unpublished === 'ok') { // That's us @@ -250,12 +258,12 @@ $(document).ready(function() { sfutest.handleRemoteJsep({ jsep: jsep }); // Check if any of the media we wanted to publish has // been rejected (e.g., wrong or unsupported codec) - var audio = msg["audio_codec"]; + let audio = msg["audio_codec"]; if(mystream && mystream.getAudioTracks() && mystream.getAudioTracks().length > 0 && !audio) { // Audio has been rejected toastr.warning("Our audio stream has been rejected, viewers won't hear us"); } - var video = msg["video_codec"]; + let video = msg["video_codec"]; if(mystream && mystream.getVideoTracks() && mystream.getVideoTracks().length > 0 && !video) { // Video has been rejected toastr.warning("Our video stream has been rejected, viewers won't see us"); @@ -273,15 +281,15 @@ $(document).ready(function() { Janus.debug(" ::: Got a local track event :::"); Janus.debug("Local track " + (on ? "added" : "removed") + ":", track); // We use the track ID as name of the element, but it may contain invalid characters - var trackId = track.id.replace(/[{}]/g, ""); + let trackId = track.id.replace(/[{}]/g, ""); if(!on) { // Track removed, get rid of the stream and the rendering - var stream = localTracks[trackId]; + let stream = localTracks[trackId]; if(stream) { try { - var tracks = stream.getTracks(); - for(var i in tracks) { - var mst = tracks[i]; + let tracks = stream.getTracks(); + for(let i in tracks) { + let mst = tracks[i]; if(mst) mst.stop(); } @@ -305,7 +313,7 @@ $(document).ready(function() { return; } // If we're here, a new track was added - var stream = localTracks[trackId]; + let stream = localTracks[trackId]; if(stream) { // We've been here already return; @@ -387,7 +395,7 @@ $(document).ready(function() { }); function checkEnter(field, event) { - var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode; + let theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode; if(theCode == 13) { registerUsername(); return false; @@ -405,7 +413,7 @@ function registerUsername() { // Try a registration $('#username').attr('disabled', true); $('#register').attr('disabled', true).unbind('click'); - var username = $('#username').val(); + let username = $('#username').val(); if(username === "") { $('#you') .removeClass().addClass('label label-warning') @@ -422,7 +430,7 @@ function registerUsername() { $('#register').removeAttr('disabled').click(registerUsername); return; } - var register = { + let register = { request: "join", room: myroom, ptype: "publisher", @@ -451,7 +459,7 @@ function publishOwnFeed(useAudio) { success: function(jsep) { Janus.debug("Got publisher SDP!"); Janus.debug(jsep); - var publish = { request: "configure", audio: useAudio, video: true }; + let publish = { request: "configure", audio: useAudio, video: true }; // You can force a specific codec to use when publishing by using the // audiocodec and videocodec properties, for instance: // publish["audiocodec"] = "opus" @@ -481,7 +489,7 @@ function publishOwnFeed(useAudio) { } function toggleMute() { - var muted = sfutest.isAudioMuted(); + let muted = sfutest.isAudioMuted(); Janus.log((muted ? "Unmuting" : "Muting") + " local stream..."); if(muted) sfutest.unmuteAudio(); @@ -494,7 +502,7 @@ function toggleMute() { function unpublishOwnFeed() { // Unpublish our stream $('#unpublish').attr('disabled', true).unbind('click'); - var unpublish = { request: "unpublish" }; + let unpublish = { request: "unpublish" }; sfutest.send({ message: unpublish }); } @@ -512,11 +520,11 @@ function subscribeTo(sources) { if(remoteFeed) { // Prepare the streams to subscribe to, as an array: we have the list of // streams the feeds are publishing, so we can choose what to pick or skip - var subscription = []; - for(var s in sources) { - var streams = sources[s]; - for(var i in streams) { - var stream = streams[i]; + let added = null, removed = null; + for(let s in sources) { + let streams = sources[s]; + for(let i in streams) { + let stream = streams[i]; // If the publisher is VP8/VP9 and this is an older Safari, let's avoid video if(stream.type === "video" && Janus.webRTCAdapter.browserDetails.browser === "safari" && (stream.codec === "vp9" || (stream.codec === "vp8" && !Janus.safariVp8))) { @@ -526,7 +534,14 @@ function subscribeTo(sources) { } if(stream.disabled) { Janus.log("Disabled stream:", stream); - // TODO Skipping for now, we should unsubscribe + // Unsubscribe + if(!removed) + removed = []; + removed.push({ + feed: stream.id, // This is mandatory + mid: stream.mid // This is optional (all streams, if missing) + }); + delete subscriptions[stream.id][stream.mid]; continue; } if(subscriptions[stream.id] && subscriptions[stream.id][stream.mid]) { @@ -535,8 +550,8 @@ function subscribeTo(sources) { } // Find an empty slot in the UI for each new source if(!feedStreams[stream.id].slot) { - var slot; - for(var i=1;i<6;i++) { + let slot; + for(let i=1;i<6;i++) { if(!feeds[i]) { slot = i; feeds[slot] = stream.id; @@ -547,7 +562,10 @@ function subscribeTo(sources) { } } } - subscription.push({ + // Subscribe + if(!added) + added = []; + added.push({ feed: stream.id, // This is mandatory mid: stream.mid // This is optional (all streams, if missing) }); @@ -556,14 +574,16 @@ function subscribeTo(sources) { subscriptions[stream.id][stream.mid] = true; } } - if(subscription.length === 0) { + if((!added || added.length === 0) && (!removed || removed.length === 0)) { // Nothing to do return; } - remoteFeed.send({ message: { - request: "subscribe", - streams: subscription - }}); + let update = { request: 'update' }; + if(added) + update.subscribe = added; + if(removed) + update.unsubscribe = removed; + remoteFeed.send({ message: update }); // Nothing else we need to do return; } @@ -580,11 +600,11 @@ function subscribeTo(sources) { Janus.log(" -- This is a multistream subscriber"); // Prepare the streams to subscribe to, as an array: we have the list of // streams the feed is publishing, so we can choose what to pick or skip - var subscription = []; - for(var s in sources) { - var streams = sources[s]; - for(var i in streams) { - var stream = streams[i]; + let subscription = []; + for(let s in sources) { + let streams = sources[s]; + for(let i in streams) { + let stream = streams[i]; // If the publisher is VP8/VP9 and this is an older Safari, let's avoid video if(stream.type === "video" && Janus.webRTCAdapter.browserDetails.browser === "safari" && (stream.codec === "vp9" || (stream.codec === "vp8" && !Janus.safariVp8))) { @@ -604,8 +624,8 @@ function subscribeTo(sources) { } // Find an empty slot in the UI for each new source if(!feedStreams[stream.id].slot) { - var slot; - for(var i=1;i<6;i++) { + let slot; + for(let i=1;i<6;i++) { if(!feeds[i]) { slot = i; feeds[slot] = stream.id; @@ -626,7 +646,7 @@ function subscribeTo(sources) { } } // We wait for the plugin to send us an offer - var subscribe = { + let subscribe = { request: "join", room: myroom, ptype: "subscriber", @@ -652,7 +672,7 @@ function subscribeTo(sources) { }, onmessage: function(msg, jsep) { Janus.debug(" ::: Got a message (subscriber) :::", msg); - var event = msg["videoroom"]; + let event = msg["videoroom"]; Janus.debug("Event: " + event); if(msg["error"]) { bootbox.alert(msg["error"]); @@ -663,14 +683,14 @@ function subscribeTo(sources) { Janus.log("Successfully attached to feed in room " + msg["room"]); } else if(event === "event") { // Check if we got an event on a simulcast-related event from this publisher - var mid = msg["mid"]; - var substream = msg["substream"]; - var temporal = msg["temporal"]; + let mid = msg["mid"]; + let substream = msg["substream"]; + let temporal = msg["temporal"]; if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) { // Check which this feed this refers to - var sub = subStreams[mid]; - var feed = feedStreams[sub.feed_id]; - var slot = slots[mid]; + let sub = subStreams[mid]; + let feed = feedStreams[sub.feed_id]; + let slot = slots[mid]; if(!simulcastStarted[slot]) { simulcastStarted[slot] = true; // Add some new buttons @@ -685,10 +705,10 @@ function subscribeTo(sources) { } if(msg["streams"]) { // Update map of subscriptions by mid - for(var i in msg["streams"]) { - var mid = msg["streams"][i]["mid"]; + for(let i in msg["streams"]) { + let mid = msg["streams"][i]["mid"]; subStreams[mid] = msg["streams"][i]; - var feed = feedStreams[msg["streams"][i]["feed_id"]]; + let feed = feedStreams[msg["streams"][i]["feed_id"]]; if(feed && feed.slot) { slots[mid] = feed.slot; mids[feed.slot] = mid; @@ -711,7 +731,7 @@ function subscribeTo(sources) { success: function(jsep) { Janus.debug("Got SDP!"); Janus.debug(jsep); - var body = { request: "start", room: myroom }; + let body = { request: "start", room: myroom }; remoteFeed.send({ message: body, jsep: jsep }); }, error: function(error) { @@ -727,10 +747,10 @@ function subscribeTo(sources) { onremotetrack: function(track, mid, on) { Janus.debug("Remote track (mid=" + mid + ") " + (on ? "added" : "removed") + ":", track); // Which publisher are we getting on this mid? - var sub = subStreams[mid]; - var feed = feedStreams[sub.feed_id]; + let sub = subStreams[mid]; + let feed = feedStreams[sub.feed_id]; Janus.debug(" >> This track is coming from feed " + sub.feed_id + ":", feed); - var slot = slots[mid]; + let slot = slots[mid]; if(feed && !slot) { slot = feed.slot; slots[mid] = feed.slot; @@ -801,11 +821,11 @@ function subscribeTo(sources) { if(!$("#videoremote" + slot + ' video').get(0)) return; // Display updated bitrate, if supported - var bitrate = remoteFeed.getBitrate(mid); + let bitrate = remoteFeed.getBitrate(mid); $('#curbitrate' + slot).text(bitrate); // Check if the resolution changed too - var width = $("#videoremote" + slot + ' video').get(0).videoWidth; - var height = $("#videoremote" + slot + ' video').get(0).videoHeight; + let width = $("#videoremote" + slot + ' video').get(0).videoWidth; + let height = $("#videoremote" + slot + ' video').get(0).videoHeight; if(width > 0 && height > 0) $('#curres' + slot).removeClass('hide').text(width+'x'+height).show(); }, 1000); @@ -814,7 +834,7 @@ function subscribeTo(sources) { }, oncleanup: function() { Janus.log(" ::: Got a cleanup notification (remote feed) :::"); - for(var i=1;i<6;i++) { + for(let i=1;i<6;i++) { $('#videoremote'+i).empty(); if(bitrateTimer[i]) clearInterval(bitrateTimer[i]); @@ -830,7 +850,7 @@ function subscribeTo(sources) { function unsubscribeFrom(id) { // Unsubscribe from this publisher - var feed = feedStreams[id]; + let feed = feedStreams[id]; if(!feed) return; Janus.debug("Feed " + id + " (" + feed.display + ") has left the room, detaching"); @@ -845,7 +865,7 @@ function unsubscribeFrom(id) { feeds.slot = 0; delete feedStreams[id]; // Send an unsubscribe request - var unsubscribe = { + let unsubscribe = { request: "unsubscribe", streams: [{ feed: id }] }; @@ -857,7 +877,7 @@ function unsubscribeFrom(id) { // Helper to parse query string function getQueryStringValue(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + let regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } @@ -865,7 +885,7 @@ function getQueryStringValue(name) { // Helper to escape XML tags function escapeXmlTags(value) { if(value) { - var escapedValue = value.replace(new RegExp('<', 'g'), '<'); + let escapedValue = value.replace(new RegExp('<', 'g'), '<'); escapedValue = escapedValue.replace(new RegExp('>', 'g'), '>'); return escapedValue; } @@ -873,7 +893,7 @@ function escapeXmlTags(value) { // Helpers to create Simulcast-related UI, if enabled function addSimulcastButtons(feed, temporal) { - var index = feed; + let index = feed; $('#remote'+index).parent().append( '
' + '
' + @@ -895,7 +915,7 @@ function addSimulcastButtons(feed, temporal) { // Enable the simulcast selection buttons $('#sl' + index + '-0').removeClass('btn-primary btn-success').addClass('btn-primary') .unbind('click').click(function() { - var index = $(this).attr('id').split('sl')[1].split('-')[0]; + let index = $(this).attr('id').split('sl')[1].split('-')[0]; toastr.info("Switching simulcast substream (mid=" + mids[index] + "), wait for it... (lower quality)", null, {timeOut: 2000}); if(!$('#sl' + index + '-2').hasClass('btn-success')) $('#sl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary'); @@ -906,7 +926,7 @@ function addSimulcastButtons(feed, temporal) { }); $('#sl' + index + '-1').removeClass('btn-primary btn-success').addClass('btn-primary') .unbind('click').click(function() { - var index = $(this).attr('id').split('sl')[1].split('-')[0]; + let index = $(this).attr('id').split('sl')[1].split('-')[0]; toastr.info("Switching simulcast substream (mid=" + mids[index] + "), wait for it... (normal quality)", null, {timeOut: 2000}); if(!$('#sl' + index + '-2').hasClass('btn-success')) $('#sl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary'); @@ -917,7 +937,7 @@ function addSimulcastButtons(feed, temporal) { }); $('#sl' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary') .unbind('click').click(function() { - var index = $(this).attr('id').split('sl')[1].split('-')[0]; + let index = $(this).attr('id').split('sl')[1].split('-')[0]; toastr.info("Switching simulcast substream (mid=" + mids[index] + "), wait for it... (higher quality)", null, {timeOut: 2000}); $('#sl' + index + '-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info'); if(!$('#sl' + index + '-1').hasClass('btn-success')) @@ -931,7 +951,7 @@ function addSimulcastButtons(feed, temporal) { $('#tl' + index + '-0').parent().removeClass('hide'); $('#tl' + index + '-0').removeClass('btn-primary btn-success').addClass('btn-primary') .unbind('click').click(function() { - var index = $(this).attr('id').split('tl')[1].split('-')[0]; + let index = $(this).attr('id').split('tl')[1].split('-')[0]; toastr.info("Capping simulcast temporal layer (mid=" + mids[index] + "), wait for it... (lowest FPS)", null, {timeOut: 2000}); if(!$('#tl' + index + '-2').hasClass('btn-success')) $('#tl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary'); @@ -942,7 +962,7 @@ function addSimulcastButtons(feed, temporal) { }); $('#tl' + index + '-1').removeClass('btn-primary btn-success').addClass('btn-primary') .unbind('click').click(function() { - var index = $(this).attr('id').split('tl')[1].split('-')[0]; + let index = $(this).attr('id').split('tl')[1].split('-')[0]; toastr.info("Capping simulcast temporal layer (mid=" + mids[index] + "), wait for it... (medium FPS)", null, {timeOut: 2000}); if(!$('#tl' + index + '-2').hasClass('btn-success')) $('#tl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary'); @@ -953,7 +973,7 @@ function addSimulcastButtons(feed, temporal) { }); $('#tl' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary') .unbind('click').click(function() { - var index = $(this).attr('id').split('tl')[1].split('-')[0]; + let index = $(this).attr('id').split('tl')[1].split('-')[0]; toastr.info("Capping simulcast temporal layer (mid=" + mids[index] + "), wait for it... (highest FPS)", null, {timeOut: 2000}); $('#tl' + index + '-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info'); if(!$('#tl' + index + '-1').hasClass('btn-success')) @@ -966,7 +986,7 @@ function addSimulcastButtons(feed, temporal) { function updateSimulcastButtons(feed, substream, temporal) { // Check the substream - var index = feed; + let index = feed; if(substream === 0) { toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000}); $('#sl' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary'); diff --git a/html/siptest.js b/html/siptest.js index a6be44f7cc6..2c1fcd4f351 100644 --- a/html/siptest.js +++ b/html/siptest.js @@ -546,7 +546,12 @@ $(document).ready(function() { if($('#videoright audio').length === 0 && $('#videoright video').length === 0) { $('#videos').removeClass('hide').show(); $('#videoright').parent().find('h3').html( - 'Send DTMF: '); + 'Send DTMF: ' + + '' + + '' + + '' + + '' + + ''); for(var i=0; i<12; i++) { if(i<10) $('#dtmf').append(''); @@ -1508,7 +1513,12 @@ function addHelper(helperCreated) { if($('#videoright' + helperId + ' audio').length === 0 && $('#videoright' + helperId + ' video').length === 0) { $('#videos' + helperId).removeClass('hide').show(); $('#videoright' + helperId).parent().find('h3').html( - 'Send DTMF: '); + 'Send DTMF: ' + + '' + + '' + + '' + + '' + + ''); for(var i=0; i<12; i++) { if(i<10) $('#dtmf' + helperId).append(''); diff --git a/package.json b/package.json index 3f543e4df1b..716c137d080 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "janus-gateway", - "version": "1.1.0", + "version": "1.1.1", "description": "A javascript library for interacting with the C based Janus WebRTC Server", "main": "html/janus.js", "types": "npm/janus.d.ts", diff --git a/src/Makefile.am b/src/Makefile.am index c8eeb339dbb..3c02a8ad86a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -203,6 +203,10 @@ transports_cflags = \ $(TRANSPORTS_CFLAGS) \ $(NULL) +transports_ldflags = \ + -version-info $(JANUS_VERSION_SO) \ + $(NULL) + transports_libadd = \ $(TRANSPORTS_LIBS) \ $(NULL) @@ -276,6 +280,10 @@ events_cflags = \ $(EVENTS_CFLAGS) \ $(NULL) +events_ldflags = \ + -version-info $(JANUS_VERSION_SO) \ + $(NULL) + events_libadd = \ $(EVENTS_LIBS) \ $(NULL) @@ -349,6 +357,10 @@ loggers_cflags = \ $(LOGGERS_CFLAGS) \ $(NULL) +loggers_ldflags = \ + -version-info $(JANUS_VERSION_SO) \ + $(NULL) + loggers_libadd = \ $(LOGGERS_LIBS) \ $(NULL) @@ -372,6 +384,10 @@ plugins_cflags = \ $(PLUGINS_CFLAGS) \ $(NULL) +plugins_ldflags = \ + -version-info $(JANUS_VERSION_SO) \ + $(NULL) + plugins_libadd = \ $(PLUGINS_LIBS) \ $(NULL) diff --git a/src/ice.c b/src/ice.c index 28b30e3e2e3..946c65b4827 100644 --- a/src/ice.c +++ b/src/ice.c @@ -1617,6 +1617,7 @@ static void janus_ice_webrtc_free(janus_ice_handle *handle) { return; } handle->agent_created = 0; + handle->agent_started = 0; if(handle->pc != NULL) { janus_ice_peerconnection_destroy(handle->pc); handle->pc = NULL; @@ -1766,6 +1767,18 @@ static void janus_ice_peerconnection_free(const janus_refcount *pc_ref) { pc->remote_candidates = NULL; g_free(pc->selected_pair); pc->selected_pair = NULL; + if(pc->payload_types != NULL) + g_hash_table_destroy(pc->payload_types); + pc->payload_types = NULL; + if(pc->clock_rates != NULL) + g_hash_table_destroy(pc->clock_rates); + pc->clock_rates = NULL; + if(pc->rtx_payload_types != NULL) + g_hash_table_destroy(pc->rtx_payload_types); + pc->rtx_payload_types = NULL; + if(pc->rtx_payload_types_rev != NULL) + g_hash_table_destroy(pc->rtx_payload_types_rev); + pc->rtx_payload_types_rev = NULL; g_free(pc); pc = NULL; } @@ -2015,6 +2028,7 @@ static void janus_ice_cb_candidate_gathering_done(NiceAgent *agent, guint stream JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } + pc->gathered = janus_get_monotonic_time(); pc->cdone = 1; /* If we're doing full-trickle, send an event to the user too */ if(janus_full_trickle_enabled) { @@ -4246,15 +4260,15 @@ static gboolean janus_ice_outgoing_stats_handle(gpointer user_data) { gint64 last = medium->in_stats.info[vindex].updated; if(!medium->in_stats.info[vindex].notified_lastsec && last && !medium->in_stats.info[vindex].bytes_lastsec && !medium->in_stats.info[vindex].bytes_lastsec_temp && - now-last >= (gint64)no_media_timer*G_USEC_PER_SEC) { + now - last >= (gint64)no_media_timer*G_USEC_PER_SEC) { /* We missed more than no_second_timer seconds of video! */ medium->in_stats.info[vindex].notified_lastsec = TRUE; if(vindex == 0) { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive %s for more than a second...\n", - handle->handle_id, medium->type == JANUS_MEDIA_VIDEO ? "video" : "audio"); + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive %s for more than %u second(s)...\n", + handle->handle_id, medium->type == JANUS_MEDIA_VIDEO ? "video" : "audio", no_media_timer); } else { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive %s (substream #%d) for more than a second...\n", - handle->handle_id, medium->type == JANUS_MEDIA_VIDEO ? "video" : "audio", vindex); + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Didn't receive %s (substream #%d) for more than %u second(s)...\n", + handle->handle_id, medium->type == JANUS_MEDIA_VIDEO ? "video" : "audio", vindex, no_media_timer); } janus_ice_notify_media(handle, medium->mid, medium->type == JANUS_MEDIA_VIDEO, medium->rtcp_ctx[1] != NULL, vindex, FALSE); } @@ -4359,6 +4373,8 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu } guint count = g_slist_length(candidates); if(pc != NULL && count > 0) { + if(handle->agent_started == 0) + handle->agent_started = janus_get_monotonic_time(); int added = nice_agent_set_remote_candidates(handle->agent, pc->stream_id, pc->component_id, candidates); if(added < 0 || (guint)added != count) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Failed to add some remote candidates (added %u, expected %u)\n", diff --git a/src/ice.h b/src/ice.h index 7dd1026ed90..491971b5a95 100644 --- a/src/ice.h +++ b/src/ice.h @@ -362,6 +362,8 @@ struct janus_ice_handle { NiceAgent *agent; /*! \brief Monotonic time of when the ICE agent has been created */ gint64 agent_created; + /*! \brief Monotonic time of when the ICE agent has been started (remote credentials set) */ + gint64 agent_started; /*! \brief ICE role (controlling or controlled) */ gboolean controlling; /*! \brief Main mid */ @@ -416,6 +418,8 @@ struct janus_ice_peerconnection { gint cdone:1; /*! \brief libnice ICE component state */ guint state; + /*! \brief Monotonic time of when gathering has completed */ + gint64 gathered; /*! \brief Monotonic time of when ICE has successfully connected */ gint64 connected; /*! \brief GLib list of libnice remote candidates for this component */ @@ -491,6 +495,14 @@ struct janus_ice_peerconnection { * or video m-line, in order to make it easier for plugins that don't do * multistream. That said, we don't plan to keep it forever */ GHashTable *media_bytype; + /*! \brief List of payload types we can expect */ + GHashTable *payload_types; + /*! \brief Mapping of payload types to their clock rates, as advertised in the SDP */ + GHashTable *clock_rates; + /*! \brief Mapping of rtx payload types to actual media-related packet types */ + GHashTable *rtx_payload_types; + /*! \brief Reverse mapping of rtx payload types to actual media-related packet types */ + GHashTable *rtx_payload_types_rev; /*! \brief Helper flag to avoid flooding the console with the same error all over again */ gboolean noerrorlog; /*! \brief Mutex to lock/unlock this stream */ diff --git a/src/janus.c b/src/janus.c index 32d6a0ca610..d8ecf010169 100644 --- a/src/janus.c +++ b/src/janus.c @@ -2992,6 +2992,8 @@ int janus_process_incoming_admin_request(janus_request *request) { json_object_set_new(info, "flags", flags); if(handle->agent) { json_object_set_new(info, "agent-created", json_integer(handle->agent_created)); + if(handle->agent_started > 0) + json_object_set_new(info, "agent-started", json_integer(handle->agent_started)); json_object_set_new(info, "ice-mode", json_string(janus_ice_is_ice_lite_enabled() ? "lite" : "full")); json_object_set_new(info, "ice-role", json_string(handle->controlling ? "controlling" : "controlled")); } @@ -3099,6 +3101,8 @@ json_t *janus_admin_peerconnection_summary(janus_ice_peerconnection *pc) { json_object_set_new(i, "failed-detected", json_integer(pc->icefailed_detected)); json_object_set_new(i, "icetimer-started", pc->icestate_source ? json_true() : json_false()); } + if(pc->gathered > 0) + json_object_set_new(i, "gathered", json_integer(pc->gathered)); if(pc->connected > 0) json_object_set_new(i, "connected", json_integer(pc->connected)); if(pc->local_candidates) { @@ -3830,12 +3834,12 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug janus_sdp_destroy(parsed_sdp); return NULL; } - if(medium->msid == NULL || strcasecmp(medium->msid, msid)) { + if(medium != NULL && (medium->msid == NULL || strcasecmp(medium->msid, msid))) { char *old_msid = medium->msid; medium->msid = g_strdup(msid); g_free(old_msid); } - if(medium->mstid == NULL || strcasecmp(medium->mstid, mstid)) { + if(medium != NULL && (medium->mstid == NULL || strcasecmp(medium->mstid, mstid))) { char *old_mstid = medium->mstid; medium->mstid = g_strdup(mstid); g_free(old_mstid); @@ -3868,7 +3872,7 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug /* If the user offered RED but the plugin rejected it, disable it */ if(opusred_pt < 0 && medium != NULL && medium->opusred_pt > 0) medium->opusred_pt = 0; - if(!have_msid) { + if(!have_msid && medium != NULL) { g_free(medium->msid); medium->msid = NULL; g_free(medium->mstid); @@ -3964,6 +3968,23 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug /* Iterate on all media */ janus_ice_peerconnection_medium *medium = NULL; uint mi=0; + /* Let's build a list of payload types first */ + if(pc->payload_types == NULL) + pc->payload_types = g_hash_table_new(NULL, NULL); + for(mi=0; mimedia); mi++) { + medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); + if(medium && medium->type != JANUS_MEDIA_DATA) { + janus_sdp_mline *m = janus_sdp_mline_find_by_index(parsed_sdp, medium->mindex); + if(m && m->ptypes) { + GList *tpt = m->ptypes; + while(tpt) { + g_hash_table_insert(pc->payload_types, tpt->data, tpt->data); + tpt = tpt->next; + } + } + } + } + /* Now let's iterate on media */ for(mi=0; mimedia); mi++) { medium = g_hash_table_lookup(pc->media, GUINT_TO_POINTER(mi)); if(medium && medium->type == JANUS_MEDIA_VIDEO && @@ -3973,33 +3994,46 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug janus_sdp_mline *m = janus_sdp_mline_find_by_index(parsed_sdp, medium->mindex); if(m && m->ptypes) { medium->rtx_payload_types = g_hash_table_new(NULL, NULL); - GList *ptypes = g_list_copy(m->ptypes), *tempP = ptypes; - GList *rtx_ptypes = g_hash_table_get_values(medium->rtx_payload_types); - while(tempP) { - int ptype = GPOINTER_TO_INT(tempP->data); - int rtx_ptype = ptype+1; - if(rtx_ptype > 127) - rtx_ptype = 96; - while(g_list_find(m->ptypes, GINT_TO_POINTER(rtx_ptype)) - || g_list_find(rtx_ptypes, GINT_TO_POINTER(rtx_ptype))) { - rtx_ptype++; + if(pc->rtx_payload_types == NULL) + pc->rtx_payload_types = g_hash_table_new(NULL, NULL); + if(pc->rtx_payload_types_rev == NULL) + pc->rtx_payload_types_rev = g_hash_table_new(NULL, NULL); + GList *ptypes = m->ptypes; + while(ptypes) { + int ptype = GPOINTER_TO_INT(ptypes->data); + if(g_hash_table_lookup(pc->rtx_payload_types_rev, GINT_TO_POINTER(ptype))) { + /* This is an RTX for an existing payload type, skip */ + ptypes = ptypes->next; + continue; + } + /* Let's check if a mapping exists already */ + int rtx_ptype = GPOINTER_TO_INT(g_hash_table_lookup(pc->rtx_payload_types, GINT_TO_POINTER(ptype))); + if(rtx_ptype == 0) { + /* No mapping yet, find one now */ + rtx_ptype = ptype+1; if(rtx_ptype > 127) rtx_ptype = 96; - if(rtx_ptype == ptype) { - /* We did a whole round? should never happen... */ - rtx_ptype = -1; - break; + while(g_hash_table_lookup(pc->payload_types, GINT_TO_POINTER(rtx_ptype)) || + g_hash_table_lookup(pc->rtx_payload_types_rev, GINT_TO_POINTER(rtx_ptype))) { + rtx_ptype++; + if(rtx_ptype > 127) + rtx_ptype = 96; + if(rtx_ptype == ptype) { + /* We did a whole round? should never happen... */ + rtx_ptype = -1; + break; + } } } - if(rtx_ptype > 0) + if(rtx_ptype > 0) { + g_hash_table_insert(pc->payload_types, GINT_TO_POINTER(rtx_ptype), GINT_TO_POINTER(rtx_ptype)); + g_hash_table_insert(pc->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + g_hash_table_insert(pc->rtx_payload_types_rev, GINT_TO_POINTER(rtx_ptype), GINT_TO_POINTER(ptype)); g_hash_table_insert(medium->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); - g_list_free(rtx_ptypes); - rtx_ptypes = g_hash_table_get_values(medium->rtx_payload_types); + } medium->do_nacks = TRUE; - tempP = tempP->next; + ptypes = ptypes->next; } - g_list_free(ptypes); - g_list_free(rtx_ptypes); } } else if(medium && medium->type == JANUS_MEDIA_VIDEO && janus_flags_is_set(&ice_handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index b4056d638cf..fd51840553d 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -4165,9 +4165,9 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s } participant->muted = muted; + JANUS_LOG(LOG_VERB, "Setting muted property: %s (room %s, user %s)\n", + participant->muted ? "true" : "false", participant->room->room_id_str, participant->user_id_str); if(participant->muted) { - JANUS_LOG(LOG_VERB, "Setting muted property: %s (room %s, user %s)\n", - participant->muted ? "true" : "false", participant->room->room_id_str, participant->user_id_str); /* Clear the queued packets waiting to be handled */ janus_mutex_lock(&participant->qmutex); while(participant->inbuf) { @@ -4185,34 +4185,32 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s janus_mutex_unlock(&participant->qmutex); } - if(audiobridge != NULL) { - json_t *list = json_array(); - json_t *pl = json_object(); - json_object_set_new(pl, "id", - string_ids ? json_string(participant->user_id_str) : json_integer(participant->user_id)); - if(participant->display) - json_object_set_new(pl, "display", json_string(participant->display)); - json_object_set_new(pl, "setup", g_atomic_int_get(&participant->session->started) ? json_true() : json_false()); - json_object_set_new(pl, "muted", participant->muted ? json_true() : json_false()); - if(audiobridge->spatial_audio) - json_object_set_new(pl, "spatial_position", json_integer(participant->spatial_position)); - json_array_append_new(list, pl); - json_t *pub = json_object(); - json_object_set_new(pub, "audiobridge", json_string("event")); - json_object_set_new(pub, "room", - string_ids ? json_string(room_id_str) : json_integer(room_id)); - json_object_set_new(pub, "participants", list); - GHashTableIter iter; - gpointer value; - g_hash_table_iter_init(&iter, audiobridge->participants); - while(g_hash_table_iter_next(&iter, NULL, &value)) { - janus_audiobridge_participant *p = value; - JANUS_LOG(LOG_VERB, "Notifying participant %s (%s)\n", p->user_id_str, p->display ? p->display : "??"); - int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, pub, NULL); - JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); - } - json_decref(pub); + json_t *list = json_array(); + json_t *pl = json_object(); + json_object_set_new(pl, "id", + string_ids ? json_string(participant->user_id_str) : json_integer(participant->user_id)); + if(participant->display) + json_object_set_new(pl, "display", json_string(participant->display)); + json_object_set_new(pl, "setup", g_atomic_int_get(&participant->session->started) ? json_true() : json_false()); + json_object_set_new(pl, "muted", participant->muted ? json_true() : json_false()); + if(audiobridge->spatial_audio) + json_object_set_new(pl, "spatial_position", json_integer(participant->spatial_position)); + json_array_append_new(list, pl); + json_t *pub = json_object(); + json_object_set_new(pub, "audiobridge", json_string("event")); + json_object_set_new(pub, "room", + string_ids ? json_string(room_id_str) : json_integer(room_id)); + json_object_set_new(pub, "participants", list); + GHashTableIter iter; + gpointer value; + g_hash_table_iter_init(&iter, audiobridge->participants); + while(g_hash_table_iter_next(&iter, NULL, &value)) { + janus_audiobridge_participant *p = value; + JANUS_LOG(LOG_VERB, "Notifying participant %s (%s)\n", p->user_id_str, p->display ? p->display : "??"); + int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, pub, NULL); + JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); } + json_decref(pub); /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { @@ -6891,7 +6889,7 @@ static void *janus_audiobridge_handler(void *data) { spatial_position = 100; participant->spatial_position = spatial_position; } - /* Notify all other participants about the mute/unmute */ + /* Notify all other participants */ janus_mutex_lock(&rooms_mutex); janus_audiobridge_room *audiobridge = participant->room; if(audiobridge != NULL) { @@ -8619,6 +8617,8 @@ static void *janus_audiobridge_participant_thread(void *data) { janus_audiobridge_relay_rtp_packet(participant->session, outpkt); } } + } + if(mixedpkt) { g_free(mixedpkt->data); g_free(mixedpkt); } diff --git a/src/plugins/janus_videoroom.c b/src/plugins/janus_videoroom.c index b2f28b5b985..c4490cc1b65 100644 --- a/src/plugins/janus_videoroom.c +++ b/src/plugins/janus_videoroom.c @@ -1048,6 +1048,7 @@ room-: { "ptype" : "subscriber", "room" : , "use_msid" : , + "autoupdate" : , "private_id" : , "streams" : [ { @@ -1827,7 +1828,7 @@ static struct janus_json_parameter configure_parameters[] = { static struct janus_json_parameter subscriber_parameters[] = { {"streams", JANUS_JSON_ARRAY, 0}, {"private_id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, - {"close_pc", JANUS_JSON_BOOL, 0}, + {"autoupdate", JANUS_JSON_BOOL, 0}, /* All the following parameters are deprecated: use streams instead */ {"audio", JANUS_JSON_BOOL, 0}, {"video", JANUS_JSON_BOOL, 0}, @@ -2283,12 +2284,12 @@ typedef struct janus_videoroom_subscriber { GHashTable *streams_bymid; /* As above, indexed by mid */ janus_mutex streams_mutex; gboolean use_msid; /* Whether we should add custom msid attributes to offers, to match publishers and streams */ - gboolean close_pc; /* Whether we should automatically close the PeerConnection when the publisher goes away */ + gboolean autoupdate; /* Whether we should trigger a renegotiation automatically when a subscribed publisher goes away */ guint32 pvt_id; /* Private ID of the participant that is subscribing (if available/provided) */ gboolean paused; gboolean kicked; /* Whether this subscription belongs to a participant that has been kicked */ gboolean e2ee; /* If media for this subscriber is end-to-end encrypted */ - volatile gint answered, pending_offer, pending_restart; + volatile gint answered, pending_offer, pending_restart, skipped_autoupdate; volatile gint destroyed; janus_refcount ref; } janus_videoroom_subscriber; @@ -8922,12 +8923,22 @@ static void janus_videoroom_hangup_media_internal(gpointer session_data) { temp = temp->next; } /* Any subscriber session to update? */ + janus_videoroom *room = participant->room; if(subscribers != NULL) { temp = subscribers; while(temp) { janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)temp->data; /* Send (or schedule) a new offer */ janus_mutex_lock(&subscriber->streams_mutex); + if(!subscriber->autoupdate || (room != NULL && !g_atomic_int_get(&room->destroyed))) { + /* ... unless we've been asked not to, or there's no room (anymore) */ + g_atomic_int_set(&subscriber->skipped_autoupdate, 1); + janus_mutex_unlock(&subscriber->streams_mutex); + janus_refcount_decrease(&subscriber->session->ref); + janus_refcount_decrease(&subscriber->ref); + temp = temp->next; + continue; + } if(!g_atomic_int_get(&subscriber->answered)) { /* We're still waiting for an answer to a previous offer, postpone this */ g_atomic_int_set(&subscriber->pending_offer, 1); @@ -9608,8 +9619,8 @@ static void *janus_videoroom_handler(void *data) { } json_t *msid = json_object_get(root, "use_msid"); gboolean use_msid = json_is_true(msid); - json_t *cpc = json_object_get(root, "close_pc"); - gboolean close_pc = cpc ? json_is_true(cpc) : TRUE; + json_t *au = json_object_get(root, "autoupdate"); + gboolean autoupdate = au ? json_is_true(au) : TRUE; /* Make sure all the feeds we're subscribing to exist */ GList *publishers = NULL; size_t i = 0; @@ -9777,7 +9788,7 @@ static void *janus_videoroom_handler(void *data) { videoroom = NULL; subscriber->pvt_id = pvt_id; subscriber->use_msid = use_msid; - subscriber->close_pc = close_pc; + subscriber->autoupdate = autoupdate; subscriber->paused = TRUE; /* We need an explicit start from the stream */ subscriber->streams_byid = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_videoroom_subscriber_stream_destroy); @@ -10942,6 +10953,8 @@ static void *janus_videoroom_handler(void *data) { } } /* We're done: check if this resulted in any actual change */ + if(g_atomic_int_compare_and_exchange(&subscriber->skipped_autoupdate, 1, 0)) + changes++; if(changes == 0) { janus_mutex_unlock(&subscriber->streams_mutex); /* Nothing changed, just ack and don't do anything else */ @@ -12141,7 +12154,7 @@ static void *janus_videoroom_handler(void *data) { mdir = JANUS_SDP_RECVONLY; } } - ps->disabled = (mdir == JANUS_SDP_INACTIVE); + ps->disabled = (m->direction == JANUS_SDP_RECVONLY || mdir == JANUS_SDP_INACTIVE); /* Add a new m-line to the answer */ if(m->type == JANUS_SDP_AUDIO) { char audio_fmtp[256]; diff --git a/src/sdp.c b/src/sdp.c index babb243bbd8..f29e5f4ab51 100644 --- a/src/sdp.c +++ b/src/sdp.c @@ -228,6 +228,13 @@ int janus_sdp_process_remote(void *ice_handle, janus_sdp *remote_sdp, gboolean r if(m->ptypes != NULL) { g_list_free(medium->payload_types); medium->payload_types = g_list_copy(m->ptypes); + if(pc->payload_types == NULL) + pc->payload_types = g_hash_table_new(NULL, NULL); + GList *temp = medium->payload_types; + while(temp) { + g_hash_table_insert(pc->payload_types, temp->data, temp->data); + temp = temp->next; + } } } else { /* Medium rejected? */ @@ -591,9 +598,21 @@ int janus_sdp_process_remote(void *ice_handle, janus_sdp *remote_sdp, gboolean r } else { rtx = TRUE; janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX); - if(medium->rtx_payload_types == NULL) - medium->rtx_payload_types = g_hash_table_new(NULL, NULL); - g_hash_table_insert(medium->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + if(pc->rtx_payload_types == NULL) + pc->rtx_payload_types = g_hash_table_new(NULL, NULL); + if(pc->rtx_payload_types_rev == NULL) + pc->rtx_payload_types_rev = g_hash_table_new(NULL, NULL); + int map_pt = GPOINTER_TO_INT(g_hash_table_lookup(pc->rtx_payload_types_rev, GINT_TO_POINTER(rtx_ptype))); + if(map_pt && map_pt != ptype) { + JANUS_LOG(LOG_WARN, "[%"SCNu64"] RTX payload type %d already mapped to %d, skipping fmtp/apt mapping with %d...\n", + handle->handle_id, rtx_ptype, map_pt, ptype); + } else { + g_hash_table_insert(pc->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + g_hash_table_insert(pc->rtx_payload_types_rev, GINT_TO_POINTER(rtx_ptype), GINT_TO_POINTER(ptype)); + if(medium->rtx_payload_types == NULL) + medium->rtx_payload_types = g_hash_table_new(NULL, NULL); + g_hash_table_insert(medium->rtx_payload_types, GINT_TO_POINTER(ptype), GINT_TO_POINTER(rtx_ptype)); + } } } } else if(!strcasecmp(a->name, "rtpmap")) { @@ -605,12 +624,21 @@ int janus_sdp_process_remote(void *ice_handle, janus_sdp *remote_sdp, gboolean r cr++; uint32_t clock_rate = 0; if(janus_string_to_uint32(cr, &clock_rate) == 0) { + if(pc->clock_rates == NULL) + pc->clock_rates = g_hash_table_new(NULL, NULL); if(medium->clock_rates == NULL) medium->clock_rates = g_hash_table_new(NULL, NULL); - g_hash_table_insert(medium->clock_rates, GINT_TO_POINTER(ptype), GUINT_TO_POINTER(clock_rate)); - /* Check if opus/red is negotiated */ - if(strstr(a->value, "red/48000/2")) - medium->opusred_pt = ptype; + uint32_t map_cr = GPOINTER_TO_UINT(g_hash_table_lookup(pc->clock_rates, GINT_TO_POINTER(ptype))); + if(map_cr && map_cr != clock_rate) { + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Payload type %d already mapped to clock rate %d, skipping rtpmap mapping with %d...\n", + handle->handle_id, ptype, map_cr, clock_rate); + } else { + g_hash_table_insert(pc->clock_rates, GINT_TO_POINTER(ptype), GUINT_TO_POINTER(clock_rate)); + g_hash_table_insert(medium->clock_rates, GINT_TO_POINTER(ptype), GUINT_TO_POINTER(clock_rate)); + /* Check if opus/red is negotiated */ + if(strstr(a->value, "red/48000/2")) + medium->opusred_pt = ptype; + } } } } @@ -693,6 +721,7 @@ int janus_sdp_process_remote(void *ice_handle, janus_sdp *remote_sdp, gboolean r /* We have a payload type that is both a codec and rtx, get rid of it */ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Removing duplicate payload type %d\n", handle->handle_id, ptype); janus_sdp_remove_payload_type(remote_sdp, medium->mindex, ptype); + g_hash_table_remove(pc->clock_rates, GINT_TO_POINTER(ptype)); g_hash_table_remove(medium->clock_rates, GINT_TO_POINTER(ptype)); } tempP = tempP->next; @@ -1659,7 +1688,8 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", medium->ssrc); m->attributes = g_list_append(m->attributes, a); if(medium->ssrc_rtx > 0 && m->type == JANUS_SDP_VIDEO && - janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { + janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX) && + (m->direction == JANUS_SDP_DEFAULT || m->direction == JANUS_SDP_SENDRECV || m->direction == JANUS_SDP_SENDONLY)) { /* Add rtx SSRC group to negotiate the RFC4588 stuff */ a = janus_sdp_attribute_create("ssrc", "%"SCNu32" cname:janus", medium->ssrc_rtx); m->attributes = g_list_append(m->attributes, a);