diff --git a/app.js b/app.js index 33d27de85..4555f979a 100644 --- a/app.js +++ b/app.js @@ -1035,26 +1035,60 @@ function getConferenceHandler() { return activecall; } +/** + * Mutes/unmutes the local video. + * + * @param mute true to mute the local video; otherwise, false + * @param options an object which specifies optional arguments such as the + * boolean key byUser with default value true which + * specifies whether the method was initiated in response to a user command (in + * contrast to an automatic decision taken by the application logic) + */ +function setVideoMute(mute, options) { + if (connection && connection.jingle.localVideo) { + var session = getConferenceHandler(); + + if (session) { + session.setVideoMute( + mute, + function (mute) { + var video = $('#video'); + var communicativeClass = "icon-camera"; + var muteClass = "icon-camera icon-camera-disabled"; + + if (mute) { + video.removeClass(communicativeClass); + video.addClass(muteClass); + } else { + video.removeClass(muteClass); + video.addClass(communicativeClass); + } + connection.emuc.addVideoInfoToPresence(mute); + connection.emuc.sendPresence(); + }, + options); + } + } +} + +$(document).on('inlastnchanged', function (event, oldValue, newValue) { + if (config.muteLocalVideoIfNotInLastN) { + setVideoMute(!newValue, { 'byUser': false }); + } +}); + +/** + * Mutes/unmutes the local video. + */ function toggleVideo() { buttonClick("#video", "icon-camera icon-camera-disabled"); - if (!(connection && connection.jingle.localVideo)) - return; - var sess = getConferenceHandler(); - if (sess) { - sess.toggleVideoMute( - function (isMuted) { - if (isMuted) { - $('#video').removeClass("icon-camera"); - $('#video').addClass("icon-camera icon-camera-disabled"); - } else { - $('#video').removeClass("icon-camera icon-camera-disabled"); - $('#video').addClass("icon-camera"); - } - connection.emuc.addVideoInfoToPresence(isMuted); - connection.emuc.sendPresence(); - } - ); + if (connection && connection.jingle.localVideo) { + var session = getConferenceHandler(); + + if (session) { + setVideoMute(!session.isVideoMute()); + } } } diff --git a/data_channels.js b/data_channels.js index 8348c7653..1787e968e 100644 --- a/data_channels.js +++ b/data_channels.js @@ -70,46 +70,65 @@ function onDataChannel(event) 'dominantspeakerchanged', [dominantSpeakerEndpoint]); } + else if ("InLastNChangeEvent" === colibriClass) + { + var oldValue = obj.oldValue; + var newValue = obj.newValue; + // Make sure that oldValue and newValue are of type boolean. + var type; + + if ((type = typeof oldValue) !== 'boolean') { + if (type === 'string') { + oldValue = (oldValue == "true"); + } else { + oldValue = new Boolean(oldValue).valueOf(); + } + } + if ((type = typeof newValue) !== 'boolean') { + if (type === 'string') { + newValue = (newValue == "true"); + } else { + newValue = new Boolean(newValue).valueOf(); + } + } + $(document).trigger('inlastnchanged', [oldValue, newValue]); + } else if ("LastNEndpointsChangeEvent" === colibriClass) { // The new/latest list of last-n endpoint IDs. var lastNEndpoints = obj.lastNEndpoints; - /* - * The list of endpoint IDs which are entering the list of - * last-n at this time i.e. were not in the old list of last-n - * endpoint IDs. - */ + // The list of endpoint IDs which are entering the list of + // last-n at this time i.e. were not in the old list of last-n + // endpoint IDs. var endpointsEnteringLastN = obj.endpointsEnteringLastN; - var stream = obj.stream; console.log( "Data channel new last-n event: ", lastNEndpoints, endpointsEnteringLastN, obj); - $(document).trigger( - 'lastnchanged', - [lastNEndpoints, endpointsEnteringLastN, stream]); + 'lastnchanged', + [lastNEndpoints, endpointsEnteringLastN, stream]); } else if ("SimulcastLayersChangedEvent" === colibriClass) { - var endpointSimulcastLayers = obj.endpointSimulcastLayers; - $(document).trigger('simulcastlayerschanged', [endpointSimulcastLayers]); + $(document).trigger( + 'simulcastlayerschanged', + [obj.endpointSimulcastLayers]); } else if ("SimulcastLayersChangingEvent" === colibriClass) { - var endpointSimulcastLayers = obj.endpointSimulcastLayers; - $(document).trigger('simulcastlayerschanging', [endpointSimulcastLayers]); + $(document).trigger( + 'simulcastlayerschanging', + [obj.endpointSimulcastLayers]); } else if ("StartSimulcastLayerEvent" === colibriClass) { - var simulcastLayer = obj.simulcastLayer; - $(document).trigger('startsimulcastlayer', simulcastLayer); + $(document).trigger('startsimulcastlayer', obj.simulcastLayer); } else if ("StopSimulcastLayerEvent" === colibriClass) { - var simulcastLayer = obj.simulcastLayer; - $(document).trigger('stopsimulcastlayer', simulcastLayer); + $(document).trigger('stopsimulcastlayer', obj.simulcastLayer); } else { @@ -141,10 +160,8 @@ function bindDataChannelListener(peerConnection) // and peer as single channel can be used for sending and receiving data. // So either channel opened by the bridge or the one opened here is enough // for communication with the bridge. - /*var dataChannelOptions = - { - reliable: true - }; +/* + var dataChannelOptions = { reliable: true }; var dataChannel = peerConnection.createDataChannel("myChannel", dataChannelOptions); @@ -157,6 +174,7 @@ function bindDataChannelListener(peerConnection) { var msgData = event.data; console.info("Got My Data Channel Message:", msgData, dataChannel); - };*/ + }; +*/ } diff --git a/libs/strophe/strophe.jingle.sessionbase.js b/libs/strophe/strophe.jingle.sessionbase.js index 230092121..87e338a01 100644 --- a/libs/strophe/strophe.jingle.sessionbase.js +++ b/libs/strophe/strophe.jingle.sessionbase.js @@ -4,10 +4,16 @@ * @param sid my session identifier(resource) * @constructor */ -function SessionBase(connection, sid){ - +function SessionBase(connection, sid) { this.connection = connection; this.sid = sid; + + /** + * The indicator which determines whether the (local) video has been muted + * in response to a user command in contrast to an automatic decision made + * by the application logic. + */ + this.muteByUser = true; } @@ -63,6 +69,7 @@ SessionBase.prototype.removeSource = function (elem, fromJid) { this.modifySources(); }; + /** * Switches video streams. * @param new_stream new stream that will be used as video of this session. @@ -230,20 +237,85 @@ SessionBase.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, } }; +/** + * Determines whether the (local) video is mute i.e. all video tracks are + * disabled. + * + * @return true if the (local) video is mute i.e. all video tracks are + * disabled; otherwise, false + */ +SessionBase.prototype.isVideoMute = function () { + var tracks = connection.jingle.localVideo.getVideoTracks(); + var mute = true; + + for (var i = 0; i < tracks.length; ++i) { + if (tracks[i].enabled) { + mute = false; + break; + } + } + return mute; +}; + +/** + * Mutes/unmutes the (local) video i.e. enables/disables all video tracks. + * + * @param mute true to mute the (local) video i.e. to disable all video + * tracks; otherwise, false + * @param callback a function to be invoked with mute after all video + * tracks have been enabled/disabled. The function may, optionally, return + * another function which is to be invoked after the whole mute/unmute operation + * has completed successfully. + * @param options an object which specifies optional arguments such as the + * boolean key byUser with default value true which + * specifies whether the method was initiated in response to a user command (in + * contrast to an automatic decision made by the application logic) + */ +SessionBase.prototype.setVideoMute = function (mute, callback, options) { + var byUser; + + if (options) { + byUser = options.byUser; + if (typeof byUser === 'undefined') { + byUser = true; + } + } else { + byUser = true; + } + // The user's command to mute the (local) video takes precedence over any + // automatic decision made by the application logic. + if (byUser) { + this.muteByUser = mute; + } else if (this.muteByUser) { + return; + } + if (mute == this.isVideoMute()) + { + // Even if no change occurs, the specified callback is to be executed. + // The specified callback may, optionally, return a successCallback + // which is to be executed as well. + var successCallback = callback(mute); + + if (successCallback) { + successCallback(); + } + } else { + var tracks = connection.jingle.localVideo.getVideoTracks(); + + for (var i = 0; i < tracks.length; ++i) { + tracks[i].enabled = !mute; + } + + if (this.peerconnection) { + this.peerconnection.hardMuteVideo(mute); + } + + this.modifySources(callback(mute)); + } +}; + // SDP-based mute by going recvonly/sendrecv // FIXME: should probably black out the screen as well SessionBase.prototype.toggleVideoMute = function (callback) { - - var ismuted = false; - var localVideo = connection.jingle.localVideo; - for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) { - ismuted = !localVideo.getVideoTracks()[idx].enabled; - } - for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) { - localVideo.getVideoTracks()[idx].enabled = !localVideo.getVideoTracks()[idx].enabled; - } - - if(this.peerconnection) - this.peerconnection.hardMuteVideo(!ismuted); - this.modifySources(callback(!ismuted)); + setVideoMute(isVideoMute(), callback); };