diff --git a/app.js b/app.js index 705dfcaa71..56bb453a2a 100644 --- a/app.js +++ b/app.js @@ -12,6 +12,10 @@ var roomName = null; var ssrc2jid = {}; var mediaStreams = {}; var bridgeIsDown = false; +//TODO: this array must be removed when firefox implement multistream support +var notReceivedSSRCs = []; + +var jid2Ssrc = {}; /** * The stats collector that process stats data and triggers updates to app.js. @@ -30,7 +34,6 @@ var localStatsCollector = null; * FIXME: remove those maps */ var ssrc2videoType = {}; -var videoSrcToSsrc = {}; /** * Currently focused video "src"(displayed in large video). * @type {String} @@ -74,26 +77,43 @@ function init() { if (RTC === null) { window.location.href = 'webrtcrequired.html'; return; - } else if (RTC.browser !== 'chrome') { + } else if (RTC.browser !== 'chrome' && + config.enableFirefoxSupport !== true) { window.location.href = 'chromeonly.html'; return; } obtainAudioAndVideoPermissions(function (stream) { - var audioStream = new webkitMediaStream(); - var videoStream = new webkitMediaStream(); - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioStream.addTrack(audioTracks[i]); + var audioStream, videoStream; + if(window.webkitMediaStream) + { + var audioStream = new webkitMediaStream(); + var videoStream = new webkitMediaStream(); + var audioTracks = stream.getAudioTracks(); + var videoTracks = stream.getVideoTracks(); + for (var i = 0; i < audioTracks.length; i++) { + audioStream.addTrack(audioTracks[i]); + } + + for (i = 0; i < videoTracks.length; i++) { + videoStream.addTrack(videoTracks[i]); + } + VideoLayout.changeLocalAudio(audioStream); + startLocalRtpStatsCollector(audioStream); + + + VideoLayout.changeLocalVideo(videoStream, true); } - VideoLayout.changeLocalAudio(audioStream); - startLocalRtpStatsCollector(audioStream); + else + { + VideoLayout.changeLocalStream(stream); + startLocalRtpStatsCollector(stream); - for (i = 0; i < videoTracks.length; i++) { - videoStream.addTrack(videoTracks[i]); } - VideoLayout.changeLocalVideo(videoStream, true); + + + + maybeDoJoin(); }); @@ -170,24 +190,33 @@ function connect(jid, password) { */ function obtainAudioAndVideoPermissions(callback) { // Get AV + var cb = function (stream) { + console.log('got', stream, stream.getAudioTracks().length, stream.getVideoTracks().length); + callback(stream); + trackUsage('localMedia', { + audio: stream.getAudioTracks().length, + video: stream.getVideoTracks().length + }); + } getUserMediaWithConstraints( ['audio', 'video'], - function (avStream) { - callback(avStream); - trackUsage('localMedia', { - audio: avStream.getAudioTracks().length, - video: avStream.getVideoTracks().length - }); - }, + cb, function (error) { - console.error('failed to obtain audio/video stream - stop', error); - trackUsage('localMediaError', { - media: error.media || 'video', - name : error.name - }); - messageHandler.showError("Error", - "Failed to obtain permissions to use the local microphone" + - "and/or camera."); + console.error('failed to obtain audio/video stream - trying audio only', error); + getUserMediaWithConstraints( + ['audio'], + cb, + function (error) { + console.error('failed to obtain audio/video stream - stop', error); + trackUsage('localMediaError', { + media: error.media || 'video', + name : error.name + }); + messageHandler.showError("Error", + "Failed to obtain permissions to use the local microphone" + + "and/or camera."); + } + ); }, config.resolution || '360'); } @@ -264,8 +293,7 @@ function doJoinAfterFocus() { connection.emuc.doJoin(roomjid); } -function waitForRemoteVideo(selector, ssrc, stream) { - +function waitForRemoteVideo(selector, ssrc, stream, jid) { // XXX(gp) so, every call to this function is *always* preceded by a call // to the RTC.attachMediaStream() function but that call is *not* followed // by an update to the videoSrcToSsrc map! @@ -297,17 +325,17 @@ function waitForRemoteVideo(selector, ssrc, stream) { // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type // in order to get rid of too many maps - if (ssrc && selector.attr('src')) { - videoSrcToSsrc[selector.attr('src')] = ssrc; + if (ssrc && jid) { + jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc; } else { - console.warn("No ssrc given for video", selector); - messageHandler.showError('Warning', 'No ssrc was given for the video.'); + console.warn("No ssrc given for jid", jid); +// messageHandler.showError('Warning', 'No ssrc was given for the video.'); } $(document).trigger('videoactive.jingle', [selector]); } else { setTimeout(function () { - waitForRemoteVideo(selector, ssrc, stream); + waitForRemoteVideo(selector, ssrc, stream, jid); }, 250); } } @@ -320,16 +348,19 @@ function waitForPresence(data, sid) { var sess = connection.jingle.sessions[sid]; var thessrc; + // look up an associated JID for a stream id - if (data.stream.id.indexOf('mixedmslabel') === -1) { + if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) { // look only at a=ssrc: and _not_ at a=ssrc-group: lines + var ssrclines = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:'); ssrclines = ssrclines.filter(function (line) { // NOTE(gp) previously we filtered on the mslabel, but that property // is not always present. // return line.indexOf('mslabel:' + data.stream.label) !== -1; - return line.indexOf('msid:' + data.stream.id) !== -1; + + return ((line.indexOf('msid:' + data.stream.id) !== -1)); }); if (ssrclines.length) { thessrc = ssrclines[0].substring(7).split(' ')[0]; @@ -359,6 +390,27 @@ function waitForPresence(data, sid) { } } + //TODO: this code should be removed when firefox implement multistream support + if(RTC.browser == "firefox") + { + if((notReceivedSSRCs.length == 0) || + !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]]) + { + // TODO(gp) limit wait duration to 1 sec. + setTimeout(function(d, s) { + return function() { + waitForPresence(d, s); + } + }(data, sid), 250); + return; + } + + thessrc = notReceivedSSRCs.pop(); + if (ssrc2jid[thessrc]) { + data.peerjid = ssrc2jid[thessrc]; + } + } + // NOTE(gp) now that we have simulcast, a media stream can have more than 1 // ssrc. We should probably take that into account in our MediaStream // wrapper. @@ -404,8 +456,6 @@ function waitForPresence(data, sid) { data.stream, data.peerjid, thessrc); - if(isVideo && container.id !== 'mixedstream') - videoSrcToSsrc[$(container).find('>video')[0].src] = thessrc; } // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 @@ -420,25 +470,6 @@ function waitForPresence(data, sid) { } } -/** - * Returns the JID of the user to whom given videoSrc belongs. - * @param videoSrc the video "src" identifier. - * @returns {null | String} the JID of the user to whom given videoSrc - * belongs. - */ -function getJidFromVideoSrc(videoSrc) -{ - if (videoSrc === localVideoSrc) - return connection.emuc.myroomjid; - - var ssrc = videoSrcToSsrc[videoSrc]; - if (!ssrc) - { - return null; - } - return ssrc2jid[ssrc]; -} - // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 function sendKeyframe(pc) { console.log('sendkeyframe', pc.iceConnectionState); @@ -637,15 +668,27 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) { var media = simulcast.parseMedia(sess.peerconnection.localDescription); media.forEach(function (media) { - // TODO(gp) maybe exclude FID streams? - Object.keys(media.sources).forEach(function(ssrc) { + if(Object.keys(media.sources).length > 0) { + // TODO(gp) maybe exclude FID streams? + Object.keys(media.sources).forEach(function (ssrc) { + newssrcs.push({ + 'ssrc': ssrc, + 'type': media.type, + 'direction': media.direction + }); + }); + } + else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type]) + { newssrcs.push({ - 'ssrc': ssrc, + 'ssrc': sess.localStreamsSSRC[media.type], 'type': media.type, 'direction': media.direction }); - }); + } + }); + console.log('new ssrcs', newssrcs); // Have to clear presence map to get rid of removed streams @@ -678,20 +721,22 @@ $(document).bind('iceconnectionstatechange.jingle', function (event, sid, sessio var metadata = {}; metadata.setupTime = (new Date()).getTime() - session.timeChecking; session.peerconnection.getStats(function (res) { - res.result().forEach(function (report) { - if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') { - metadata.localCandidateType = report.stat('googLocalCandidateType'); - metadata.remoteCandidateType = report.stat('googRemoteCandidateType'); - - // log pair as well so we can get nice pie charts - metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType'); - - if (report.stat('googRemoteAddress').indexOf('[') === 0) { - metadata.ipv6 = true; + if(res && res.result) { + res.result().forEach(function (report) { + if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') { + metadata.localCandidateType = report.stat('googLocalCandidateType'); + metadata.remoteCandidateType = report.stat('googRemoteCandidateType'); + + // log pair as well so we can get nice pie charts + metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType'); + + if (report.stat('googRemoteAddress').indexOf('[') === 0) { + metadata.ipv6 = true; + } } - } - }); - trackUsage('iceConnected', metadata); + }); + trackUsage('iceConnected', metadata); + } }); } break; @@ -788,14 +833,13 @@ $(document).bind('left.muc', function (event, jid) { APIConnector.triggerEvent("participantLeft",{jid: jid}); } + delete jid2Ssrc[jid]; + // Unlock large video - if (focusedVideoSrc) + if (focusedVideoSrc && focusedVideoSrc.jid === jid) { - if (getJidFromVideoSrc(focusedVideoSrc) === jid) - { - console.info("Focused video owner has left the conference"); - focusedVideoSrc = null; - } + console.info("Focused video owner has left the conference"); + focusedVideoSrc = null; } connection.jingle.terminateByJid(jid); @@ -812,8 +856,6 @@ $(document).bind('presence.muc', function (event, jid, info, pres) { Object.keys(ssrc2jid).forEach(function (ssrc) { if (ssrc2jid[ssrc] == jid) { delete ssrc2jid[ssrc]; - } - if (ssrc2videoType[ssrc] == jid) { delete ssrc2videoType[ssrc]; } }); @@ -822,6 +864,7 @@ $(document).bind('presence.muc', function (event, jid, info, pres) { //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc')); var ssrcV = ssrc.getAttribute('ssrc'); ssrc2jid[ssrcV] = jid; + notReceivedSSRCs.push(ssrcV); var type = ssrc.getAttribute('type'); ssrc2videoType[ssrcV] = type; @@ -949,16 +992,20 @@ $(document).bind('passwordrequired.main', function (event) { * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 * @returns {boolean} */ -function isVideoSrcDesktop(videoSrc) { +function isVideoSrcDesktop(jid) { // FIXME: fix this mapping mess... // figure out if large video is desktop stream or just a camera + + if(!jid) + return false; var isDesktop = false; - if (localVideoSrc === videoSrc) { + if (connection.emuc.myroomjid && + Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) { // local video isDesktop = isUsingScreenStream; } else { // Do we have associations... - var videoSsrc = videoSrcToSsrc[videoSrc]; + var videoSsrc = jid2Ssrc[jid]; if (videoSsrc) { var videoType = ssrc2videoType[videoSsrc]; if (videoType) { @@ -968,7 +1015,7 @@ function isVideoSrcDesktop(videoSrc) { console.error("No video type for ssrc: " + videoSsrc); } } else { - console.error("No ssrc for src: " + videoSrc); + console.error("No ssrc for jid: " + jid); } } return isDesktop; @@ -1311,6 +1358,8 @@ $(document).ready(function () { VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight); }); + document.getElementById('largeVideo').volume = 0; + if (!$('#settings').is(':visible')) { console.log('init'); init(); @@ -1386,10 +1435,10 @@ function disposeConference(onUnload) { // FIXME: probably removing streams is not required and close() should // be enough if (connection.jingle.localAudio) { - handler.peerconnection.removeStream(connection.jingle.localAudio); + handler.peerconnection.removeStream(connection.jingle.localAudio, onUnload); } if (connection.jingle.localVideo) { - handler.peerconnection.removeStream(connection.jingle.localVideo); + handler.peerconnection.removeStream(connection.jingle.localVideo, onUnload); } handler.peerconnection.close(); } @@ -1553,7 +1602,7 @@ function onSelectedEndpointChanged(userJid) dataChannel.send(JSON.stringify({ 'colibriClass': 'SelectedEndpointChangedEvent', 'selectedEndpoint': (!userJid || userJid == null) - ? null : Strophe.getResourceFromJid(userJid) + ? null : userJid })); return true; diff --git a/config.js b/config.js index 5c2fef5bf2..716c7c332b 100644 --- a/config.js +++ b/config.js @@ -4,7 +4,7 @@ var config = { //anonymousdomain: 'guest.example.com', muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030 bridge: 'jitsi-videobridge.jitsi-meet.example.com', // FIXME: use XEP-0030 - call_control: 'callcontrol.jitsi-meet.example.com', + //call_control: 'callcontrol.jitsi-meet.example.com', focus: 'focus.jitsi-meet.example.com' }, // getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; }, @@ -26,8 +26,10 @@ var config = { adaptiveSimulcast: false, useRtcpMux: true, useBundle: true, - enableRecording: true, - enableWelcomePage: false, + enableRecording: false, + enableWelcomePage: true, enableSimulcast: false, + enableFirefoxSupport: false, //firefox support is still experimental, only one-to-one conferences with chrome focus + // will work when simulcast, bundle, mux, lastN and SCTP are disabled. logStats: false // Enable logging of PeerConnection stats via the focus }; diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 401c6f69d6..4b2e2276ba 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -104,6 +104,11 @@ text-align: center; } +#largeVideo +{ + object-fit: cover; +} + #presentation, #etherpad, #localVideoWrapper>video, diff --git a/data_channels.js b/data_channels.js index 57a8135745..8348c76536 100644 --- a/data_channels.js +++ b/data_channels.js @@ -26,8 +26,7 @@ function onDataChannel(event) // when the data channel becomes available, tell the bridge about video // selections so that it can do adaptive simulcast, - var largeVideoSrc = $('#largeVideo').attr('src'); - var userJid = getJidFromVideoSrc(largeVideoSrc); + var userJid = VideoLayout.getLargeVideoState().userJid; // we want the notification to trigger even if userJid is undefined, // or null. onSelectedEndpointChanged(userJid); diff --git a/libs/colibri/colibri.focus.js b/libs/colibri/colibri.focus.js index 67e38c3c59..310fe3a670 100644 --- a/libs/colibri/colibri.focus.js +++ b/libs/colibri/colibri.focus.js @@ -453,7 +453,8 @@ ColibriFocus.prototype.createdConference = function (result) { 'a=rtpmap:100 VP8/90000\r\n' + 'a=rtcp-fb:100 ccm fir\r\n' + 'a=rtcp-fb:100 nack\r\n' + - 'a=rtcp-fb:100 goog-remb\r\n' + + 'a=rtcp-fb:100 nack pli\r\n' + + (config.enableFirefoxSupport? "" : 'a=rtcp-fb:100 goog-remb\r\n') + 'a=rtpmap:116 red/90000\r\n' + 'a=rtpmap:117 ulpfec/90000\r\n' + (config.useRtcpMux ? 'a=rtcp-mux\r\n' : '') + diff --git a/libs/strophe/strophe.jingle.adapter.js b/libs/strophe/strophe.jingle.adapter.js index 5427db6749..7de262361a 100644 --- a/libs/strophe/strophe.jingle.adapter.js +++ b/libs/strophe/strophe.jingle.adapter.js @@ -141,12 +141,28 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) { TraceablePeerConnection.prototype.addStream = function (stream) { this.trace('addStream', stream.id); simulcast.resetSender(); - this.peerconnection.addStream(stream); + try + { + this.peerconnection.addStream(stream); + } + catch (e) + { + console.error(e); + return; + } }; -TraceablePeerConnection.prototype.removeStream = function (stream) { +TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) { this.trace('removeStream', stream.id); simulcast.resetSender(); + if(stopStreams) { + stream.getAudioTracks().forEach(function (track) { + track.stop(); + }); + stream.getVideoTracks().forEach(function (track) { + track.stop(); + }); + } this.peerconnection.removeStream(stream); }; @@ -486,6 +502,11 @@ TraceablePeerConnection.prototype.addIceCandidate = function (candidate, success TraceablePeerConnection.prototype.getStats = function(callback, errback) { if (navigator.mozGetUserMedia) { // ignore for now... + if(!errback) + errback = function () { + + } + this.peerconnection.getStats(null,callback,errback); } else { this.peerconnection.getStats(callback); } @@ -506,7 +527,40 @@ function setupRTC() { element[0].mozSrcObject = stream; element[0].play(); }, - pc_constraints: {} + pc_constraints: {}, + getLocalSSRC: function (session, callback) { + session.peerconnection.getStats(function (s) { + var ssrcs = {}; + s.forEach(function (item) { + if (item.type == "outboundrtp" && !item.isRemote) + { + ssrcs[item.id.split('_')[2]] = item.ssrc; + } + }); + session.localStreamsSSRC = { + "audio": ssrcs.audio,//for stable 0 + "video": ssrcs.video// for stable 1 + }; + callback(session.localStreamsSSRC); + }, + function () { + callback(null); + }); + }, + getStreamID: function (stream) { + var tracks = stream.getVideoTracks(); + if(!tracks || tracks.length == 0) + { + tracks = stream.getAudioTracks(); + } + return tracks[0].id.replace(/[\{,\}]/g,""); + }, + getVideoSrc: function (element) { + return element.mozSrcObject; + }, + setVideoSrc: function (element, src) { + element.mozSrcObject = src; + } }; if (!MediaStream.prototype.getVideoTracks) MediaStream.prototype.getVideoTracks = function () { return []; }; @@ -525,7 +579,19 @@ function setupRTC() { element.attr('src', webkitURL.createObjectURL(stream)); }, // DTLS should now be enabled by default but.. - pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]} + pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}, + getLocalSSRC: function (session, callback) { + callback(null); + }, + getStreamID: function (stream) { + return stream.id; + }, + getVideoSrc: function (element) { + return element.getAttribute("src"); + }, + setVideoSrc: function (element, src) { + element.setAttribute("src", src); + } }; if (navigator.userAgent.indexOf('Android') != -1) { RTC.pc_constraints = {}; // disable DTLS on Android diff --git a/libs/strophe/strophe.jingle.js b/libs/strophe/strophe.jingle.js index 20f456f5e2..e5c2b2ab98 100644 --- a/libs/strophe/strophe.jingle.js +++ b/libs/strophe/strophe.jingle.js @@ -88,7 +88,9 @@ Strophe.addConnectionPlugin('jingle', { case 'session-initiate': sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection); // configure session - if (this.localAudio) { + + //in firefox we have only one stream object + if (this.localAudio != this.localVideo) { sess.localStreams.push(this.localAudio); } if (this.localVideo) { @@ -173,7 +175,9 @@ Strophe.addConnectionPlugin('jingle', { Math.random().toString(36).substr(2, 12), // random string this.connection); // configure session - if (this.localAudio) { + + //in firefox we have only one stream + if (this.localAudio != this.localVideo) { sess.localStreams.push(this.localAudio); } if (this.localVideo) { diff --git a/libs/strophe/strophe.jingle.sdp.js b/libs/strophe/strophe.jingle.sdp.js index e78e0682a1..edfa48755b 100644 --- a/libs/strophe/strophe.jingle.sdp.js +++ b/libs/strophe/strophe.jingle.sdp.js @@ -194,7 +194,8 @@ SDP.prototype.removeMediaLines = function(mediaindex, prefix) { } // add content's to a jingle element -SDP.prototype.toJingle = function (elem, thecreator) { +SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { +// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]); var i, j, k, mline, ssrc, rtpmap, tmp, line, lines; var self = this; // new bundle plan @@ -221,7 +222,12 @@ SDP.prototype.toJingle = function (elem, thecreator) { if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) { ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first } else { - ssrc = false; + if(ssrcs && ssrcs[mline.media]) + { + ssrc = ssrcs[mline.media]; + } + else + ssrc = false; } elem.c('content', {creator: thecreator, name: mline.media}); @@ -267,25 +273,60 @@ SDP.prototype.toJingle = function (elem, thecreator) { elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); // FIXME: group by ssrc and support multiple different ssrcs var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:'); - ssrclines.forEach(function(line) { - idx = line.indexOf(' '); - var linessrc = line.substr(0, idx).substr(7); - if (linessrc != ssrc) { + if(ssrclines.length > 0) { + ssrclines.forEach(function (line) { + idx = line.indexOf(' '); + var linessrc = line.substr(0, idx).substr(7); + if (linessrc != ssrc) { + elem.up(); + ssrc = linessrc; + elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + } + var kv = line.substr(idx + 1); + elem.c('parameter'); + if (kv.indexOf(':') == -1) { + elem.attrs({ name: kv }); + } else { + elem.attrs({ name: kv.split(':', 2)[0] }); + elem.attrs({ value: kv.split(':', 2)[1] }); + } elem.up(); - ssrc = linessrc; - elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - } - var kv = line.substr(idx + 1); + }); + elem.up(); + } + else + { + elem.up(); + elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); elem.c('parameter'); - if (kv.indexOf(':') == -1) { - elem.attrs({ name: kv }); - } else { - elem.attrs({ name: kv.split(':', 2)[0] }); - elem.attrs({ value: kv.split(':', 2)[1] }); - } + elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)}); elem.up(); - }); - elem.up(); + var msid = null; + if(mline.media == "audio") + { + msid = connection.jingle.localAudio.getAudioTracks()[0].id; + } + else + { + msid = connection.jingle.localVideo.getVideoTracks()[0].id; + } + if(msid != null) + { + msid = msid.replace(/[\{,\}]/g,""); + elem.c('parameter'); + elem.attrs({name: "msid", value:msid}); + elem.up(); + elem.c('parameter'); + elem.attrs({name: "mslabel", value:msid}); + elem.up(); + elem.c('parameter'); + elem.attrs({name: "label", value:msid}); + elem.up(); + elem.up(); + } + + + } // XEP-0339 handle ssrc-group attributes var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:'); diff --git a/libs/strophe/strophe.jingle.session.js b/libs/strophe/strophe.jingle.session.js index 6ba2c6e870..711ea39779 100644 --- a/libs/strophe/strophe.jingle.session.js +++ b/libs/strophe/strophe.jingle.session.js @@ -36,6 +36,7 @@ function JingleSession(me, sid, connection) { this.reason = null; this.wait = true; + this.localStreamsSSRC = null; } JingleSession.prototype.initiate = function (peerjid, isInitiator) { @@ -64,6 +65,7 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { }; this.peerconnection.onaddstream = function (event) { self.remoteStreams.push(event.stream); + console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id); $(document).trigger('remotestreamadded.jingle', [event, self.sid]); }; this.peerconnection.onremovestream = function (event) { @@ -128,8 +130,7 @@ JingleSession.prototype.accept = function () { initiator: this.initiator, responder: this.responder, sid: this.sid }); - prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder'); - + prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC); var sdp = this.peerconnection.localDescription.sdp; while (SDPUtil.find_line(sdp, 'a=inactive')) { // FIXME: change any inactive to sendrecv or whatever they were originally @@ -149,7 +150,7 @@ JingleSession.prototype.accept = function () { function (stanza) { var error = ($(stanza).find('error').length) ? { code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, + reason: $(stanza).find('error :first')[0].tagName }:{}; error.source = 'answer'; $(document).trigger('error.jingle', [self.sid, error]); @@ -220,10 +221,10 @@ JingleSession.prototype.sendIceCandidate = function (candidate) { }, 20); } - this.drip_container.push(event.candidate); + this.drip_container.push(candidate); return; } else { - self.sendIceCandidate([event.candidate]); + self.sendIceCandidate([candidate]); } } } else { @@ -237,25 +238,43 @@ JingleSession.prototype.sendIceCandidate = function (candidate) { initiator: this.initiator, sid: this.sid}); this.localSDP = new SDP(this.peerconnection.localDescription.sdp); - this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder'); - this.connection.sendIQ(init, - function () { - //console.log('session initiate ack'); - var ack = {}; - ack.source = 'offer'; - $(document).trigger('ack.jingle', [self.sid, ack]); - }, - function (stanza) { - self.state = 'error'; - self.peerconnection.close(); - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, - }:{}; - error.source = 'offer'; - $(document).trigger('error.jingle', [self.sid, error]); - }, - 10000); + var self = this; + var sendJingle = function (ssrc) { + if(!ssrc) + ssrc = {}; + self.localSDP.toJingle(init, self.initiator == self.me ? 'initiator' : 'responder', ssrc); + self.connection.sendIQ(init, + function () { + //console.log('session initiate ack'); + var ack = {}; + ack.source = 'offer'; + $(document).trigger('ack.jingle', [self.sid, ack]); + }, + function (stanza) { + self.state = 'error'; + self.peerconnection.close(); + var error = ($(stanza).find('error').length) ? { + code: $(stanza).find('error').attr('code'), + reason: $(stanza).find('error :first')[0].tagName, + }:{}; + error.source = 'offer'; + $(document).trigger('error.jingle', [self.sid, error]); + }, + 10000); + } + + RTC.getLocalSSRC(this, function (ssrcs) { + if(ssrcs) + { + sendJingle(ssrcs); + $(document).trigger("setLocalDescription.jingle", [self.sid]); + } + else + { + sendJingle(); + } + }); + } this.lasticecandidate = true; console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate); @@ -276,11 +295,12 @@ JingleSession.prototype.sendIceCandidates = function (candidates) { sid: this.sid}); for (var mid = 0; mid < this.localSDP.media.length; mid++) { var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; }); + var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]); if (cands.length > 0) { var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session); ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1'; cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder', - name: cands[0].sdpMid + name: (cands[0].sdpMid? cands[0].sdpMid : mline.media) }).c('transport', ice); for (var i = 0; i < cands.length; i++) { cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up(); @@ -339,14 +359,14 @@ JingleSession.prototype.createdOffer = function (sdp) { var self = this; this.localSDP = new SDP(sdp.sdp); //this.localSDP.mangle(); - if (this.usetrickle) { + var sendJingle = function () { var init = $iq({to: this.peerjid, type: 'set'}) .c('jingle', {xmlns: 'urn:xmpp:jingle:1', action: 'session-initiate', initiator: this.initiator, sid: this.sid}); - this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder'); + this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC); this.connection.sendIQ(init, function () { var ack = {}; @@ -368,7 +388,16 @@ JingleSession.prototype.createdOffer = function (sdp) { sdp.sdp = this.localSDP.raw; this.peerconnection.setLocalDescription(sdp, function () { - $(document).trigger('setLocalDescription.jingle', [self.sid]); + if(this.usetrickle) + { + RTC.getLocalSSRC(function(ssrc) + { + sendJingle(ssrc); + $(document).trigger('setLocalDescription.jingle', [self.sid]); + }); + } + else + $(document).trigger('setLocalDescription.jingle', [self.sid]); //console.log('setLocalDescription success'); }, function (e) { @@ -557,21 +586,9 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { var self = this; this.localSDP = new SDP(sdp.sdp); //this.localSDP.mangle(); - var accept = null; this.usepranswer = provisional === true; if (this.usetrickle) { - if (!this.usepranswer) { - accept = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-accept', - initiator: this.initiator, - responder: this.responder, - sid: this.sid }); - var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp); - var publicLocalSDP = new SDP(publicLocalDesc.sdp); - publicLocalSDP.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder'); - } else { + if (this.usepranswer) { sdp.type = 'pranswer'; for (var i = 0; i < this.localSDP.media.length; i++) { this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n'); @@ -579,13 +596,19 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join(''); } } - sdp.sdp = this.localSDP.raw; - this.peerconnection.setLocalDescription(sdp, - function () { - $(document).trigger('setLocalDescription.jingle', [self.sid]); - //console.log('setLocalDescription success'); - if (accept) - { + var self = this; + var sendJingle = function (ssrcs) { + + var accept = $iq({to: self.peerjid, + type: 'set'}) + .c('jingle', {xmlns: 'urn:xmpp:jingle:1', + action: 'session-accept', + initiator: self.initiator, + responder: self.responder, + sid: self.sid }); + var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp); + var publicLocalSDP = new SDP(publicLocalDesc.sdp); + publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs); this.connection.sendIQ(accept, function () { var ack = {}; @@ -598,12 +621,23 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { reason: $(stanza).find('error :first')[0].tagName, }:{}; error.source = 'answer'; - error.stanza = stanza; - $(document).trigger('error.jingle', [self.sid, error]); }, 10000); + } + sdp.sdp = this.localSDP.raw; + this.peerconnection.setLocalDescription(sdp, + function () { + + //console.log('setLocalDescription success'); + if (self.usetrickle && !self.usepranswer) { + RTC.getLocalSSRC(self, function (ssrc) { + sendJingle(ssrc); + $(document).trigger('setLocalDescription.jingle', [self.sid]); + }); } + else + $(document).trigger('setLocalDescription.jingle', [self.sid]); }, function (e) { console.error('setLocalDescription failed', e); diff --git a/libs/strophe/strophe.jingle.sessionbase.js b/libs/strophe/strophe.jingle.sessionbase.js index 2e5a3a883a..2300921214 100644 --- a/libs/strophe/strophe.jingle.sessionbase.js +++ b/libs/strophe/strophe.jingle.sessionbase.js @@ -82,14 +82,17 @@ SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_c if(self.peerconnection.localDescription) { oldSdp = new SDP(self.peerconnection.localDescription.sdp); } - self.peerconnection.removeStream(oldStream); + self.peerconnection.removeStream(oldStream, true); self.peerconnection.addStream(new_stream); } self.connection.jingle.localVideo = new_stream; self.connection.jingle.localStreams = []; - self.connection.jingle.localStreams.push(self.connection.jingle.localAudio); + + //in firefox we have only one stream object + if(self.connection.jingle.localAudio != self.connection.jingle.localVideo) + self.connection.jingle.localStreams.push(self.connection.jingle.localAudio); self.connection.jingle.localStreams.push(self.connection.jingle.localVideo); // Conference is not active diff --git a/libs/strophe/strophe.min.js b/libs/strophe/strophe.min.js index 9f53dd951e..6262a90ec7 100644 --- a/libs/strophe/strophe.min.js +++ b/libs/strophe/strophe.min.js @@ -1 +1 @@ -var Base64=(function(){var keyStr="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var obj={encode:function(input){var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;do{chr1=input.charCodeAt(i++);chr2=input.charCodeAt(i++);chr3=input.charCodeAt(i++);enc1=chr1>>2;enc2=((chr1&3)<<4)|(chr2>>4);enc3=((chr2&15)<<2)|(chr3>>6);enc4=chr3&63;if(isNaN(chr2)){enc3=enc4=64}else{if(isNaN(chr3)){enc4=64}}output=output+keyStr.charAt(enc1)+keyStr.charAt(enc2)+keyStr.charAt(enc3)+keyStr.charAt(enc4)}while(i>4);chr2=((enc2&15)<<4)|(enc3>>2);chr3=((enc3&3)<<6)|enc4;output=output+String.fromCharCode(chr1);if(enc3!=64){output=output+String.fromCharCode(chr2)}if(enc4!=64){output=output+String.fromCharCode(chr3)}}while(i>5]|=128<<(24-len%32);x[((len+64>>9)<<4)+15]=len;var w=new Array(80);var a=1732584193;var b=-271733879;var c=-1732584194;var d=271733878;var e=-1009589776;var i,j,t,olda,oldb,oldc,oldd,olde;for(i=0;i16){bkey=core_sha1(bkey,key.length*8)}var ipad=new Array(16),opad=new Array(16);for(var i=0;i<16;i++){ipad[i]=bkey[i]^909522486;opad[i]=bkey[i]^1549556828}var hash=core_sha1(ipad.concat(str2binb(data)),512+data.length*8);return core_sha1(opad.concat(hash),512+160)}function safe_add(x,y){var lsw=(x&65535)+(y&65535);var msw=(x>>16)+(y>>16)+(lsw>>16);return(msw<<16)|(lsw&65535)}function rol(num,cnt){return(num<>>(32-cnt))}function str2binb(str){var bin=[];var mask=255;for(var i=0;i>5]|=(str.charCodeAt(i/8)&mask)<<(24-i%32)}return bin}function binb2str(bin){var str="";var mask=255;for(var i=0;i>5]>>>(24-i%32))&mask)}return str}function binb2b64(binarray){var tab="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var str="";var triplet,j;for(var i=0;i>2]>>8*(3-i%4))&255)<<16)|(((binarray[i+1>>2]>>8*(3-(i+1)%4))&255)<<8)|((binarray[i+2>>2]>>8*(3-(i+2)%4))&255);for(j=0;j<4;j++){if(i*8+j*6>binarray.length*32){str+="="}else{str+=tab.charAt((triplet>>6*(3-j))&63)}}}return str}var MD5=(function(){var safe_add=function(x,y){var lsw=(x&65535)+(y&65535);var msw=(x>>16)+(y>>16)+(lsw>>16);return(msw<<16)|(lsw&65535)};var bit_rol=function(num,cnt){return(num<>>(32-cnt))};var str2binl=function(str){var bin=[];for(var i=0;i>5]|=(str.charCodeAt(i/8)&255)<<(i%32)}return bin};var binl2str=function(bin){var str="";for(var i=0;i>5]>>>(i%32))&255)}return str};var binl2hex=function(binarray){var hex_tab="0123456789abcdef";var str="";for(var i=0;i>2]>>((i%4)*8+4))&15)+hex_tab.charAt((binarray[i>>2]>>((i%4)*8))&15)}return str};var md5_cmn=function(q,a,b,x,s,t){return safe_add(bit_rol(safe_add(safe_add(a,q),safe_add(x,t)),s),b)};var md5_ff=function(a,b,c,d,x,s,t){return md5_cmn((b&c)|((~b)&d),a,b,x,s,t)};var md5_gg=function(a,b,c,d,x,s,t){return md5_cmn((b&d)|(c&(~d)),a,b,x,s,t)};var md5_hh=function(a,b,c,d,x,s,t){return md5_cmn(b^c^d,a,b,x,s,t)};var md5_ii=function(a,b,c,d,x,s,t){return md5_cmn(c^(b|(~d)),a,b,x,s,t)};var core_md5=function(x,len){x[len>>5]|=128<<((len)%32);x[(((len+64)>>>9)<<4)+14]=len;var a=1732584193;var b=-271733879;var c=-1732584194;var d=271733878;var olda,oldb,oldc,oldd;for(var i=0;i0){for(var i=0;i/g,">");text=text.replace(/'/g,"'");text=text.replace(/"/g,""");return text},xmlTextNode:function(text){return Strophe.xmlGenerator().createTextNode(text)},xmlHtmlNode:function(html){var node;if(window.DOMParser){var parser=new DOMParser();node=parser.parseFromString(html,"text/xml")}else{node=new ActiveXObject("Microsoft.XMLDOM");node.async="false";node.loadXML(html)}return node},getText:function(elem){if(!elem){return null}var str="";if(elem.childNodes.length===0&&elem.nodeType==Strophe.ElementType.TEXT){str+=elem.nodeValue}for(var i=0;i0){value=css.join("; ");el.setAttribute(attribute,value)}}else{el.setAttribute(attribute,value)}}for(i=0;i/g,"\\3e").replace(/@/g,"\\40")},unescapeNode:function(node){return node.replace(/\\20/g," ").replace(/\\22/g,'"').replace(/\\26/g,"&").replace(/\\27/g,"'").replace(/\\2f/g,"/").replace(/\\3a/g,":").replace(/\\3c/g,"<").replace(/\\3e/g,">").replace(/\\40/g,"@").replace(/\\5c/g,"\\")},getNodeFromJid:function(jid){if(jid.indexOf("@")<0){return null}return jid.split("@")[0]},getDomainFromJid:function(jid){var bare=Strophe.getBareJidFromJid(jid);if(bare.indexOf("@")<0){return bare}else{var parts=bare.split("@");parts.splice(0,1);return parts.join("@")}},getResourceFromJid:function(jid){var s=jid.split("/");if(s.length<2){return null}s.splice(0,1);return s.join("/")},getBareJidFromJid:function(jid){return jid?jid.split("/")[0]:null},log:function(level,msg){return},debug:function(msg){this.log(this.LogLevel.DEBUG,msg)},info:function(msg){this.log(this.LogLevel.INFO,msg)},warn:function(msg){this.log(this.LogLevel.WARN,msg)},error:function(msg){this.log(this.LogLevel.ERROR,msg)},fatal:function(msg){this.log(this.LogLevel.FATAL,msg)},serialize:function(elem){var result;if(!elem){return null}if(typeof(elem.tree)==="function"){elem=elem.tree()}var nodeName=elem.nodeName;var i,child;if(elem.getAttribute("_realname")){nodeName=elem.getAttribute("_realname")}result="<"+nodeName;for(i=0;i/g,">").replace(/0){result+=">";for(i=0;i"}}result+=""}else{result+="/>"}return result},_requestId:0,_connectionPlugins:{},addConnectionPlugin:function(name,ptype){Strophe._connectionPlugins[name]=ptype}};Strophe.Builder=function(name,attrs){if(name=="presence"||name=="message"||name=="iq"){if(attrs&&!attrs.xmlns){attrs.xmlns=Strophe.NS.CLIENT}else{if(!attrs){attrs={xmlns:Strophe.NS.CLIENT}}}}this.nodeTree=Strophe.xmlElement(name,attrs);this.node=this.nodeTree};Strophe.Builder.prototype={tree:function(){return this.nodeTree},toString:function(){return Strophe.serialize(this.nodeTree)},up:function(){this.node=this.node.parentNode;return this},attrs:function(moreattrs){for(var k in moreattrs){if(moreattrs.hasOwnProperty(k)){this.node.setAttribute(k,moreattrs[k])}}return this},c:function(name,attrs,text){var child=Strophe.xmlElement(name,attrs,text);this.node.appendChild(child);if(!text){this.node=child}return this},cnode:function(elem){var impNode;var xmlGen=Strophe.xmlGenerator();try{impNode=(xmlGen.importNode!==undefined)}catch(e){impNode=false}var newElem=impNode?xmlGen.importNode(elem,true):Strophe.copyElement(elem);this.node.appendChild(newElem);this.node=newElem;return this},t:function(text){var child=Strophe.xmlTextNode(text);this.node.appendChild(child);return this},h:function(html){var fragment=document.createElement("body");fragment.innerHTML=html;var xhtml=Strophe.createHtml(fragment);while(xhtml.childNodes.length>0){this.node.appendChild(xhtml.childNodes[0])}return this}};Strophe.Handler=function(handler,ns,name,type,id,from,options){this.handler=handler;this.ns=ns;this.name=name;this.type=type;this.id=id;this.options=options||{matchBare:false};if(!this.options.matchBare){this.options.matchBare=false}if(this.options.matchBare){this.from=from?Strophe.getBareJidFromJid(from):null}else{this.from=from}this.user=true};Strophe.Handler.prototype={isMatch:function(elem){var nsMatch;var from=null;if(this.options.matchBare){from=Strophe.getBareJidFromJid(elem.getAttribute("from"))}else{from=elem.getAttribute("from")}nsMatch=false;if(!this.ns){nsMatch=true}else{var that=this;Strophe.forEachChild(elem,null,function(elem){if(elem.getAttribute("xmlns")==that.ns){nsMatch=true}});nsMatch=nsMatch||elem.getAttribute("xmlns")==this.ns}if(nsMatch&&(!this.name||Strophe.isTagEqual(elem,this.name))&&(!this.type||elem.getAttribute("type")==this.type)&&(!this.id||elem.getAttribute("id")==this.id)&&(!this.from||from==this.from)){return true}return false},run:function(elem){var result=null;try{result=this.handler(elem)}catch(e){if(e.sourceURL){Strophe.fatal("error: "+this.handler+" "+e.sourceURL+":"+e.line+" - "+e.name+": "+e.message)}else{if(e.fileName){if(typeof(console)!="undefined"){console.trace();console.error(this.handler," - error - ",e,e.message)}Strophe.fatal("error: "+this.handler+" "+e.fileName+":"+e.lineNumber+" - "+e.name+": "+e.message)}else{Strophe.fatal("error: "+e.message+"\n"+e.stack)}}throw e}return result},toString:function(){return"{Handler: "+this.handler+"("+this.name+","+this.id+","+this.ns+")}"}};Strophe.TimedHandler=function(period,handler){this.period=period;this.handler=handler;this.lastCalled=new Date().getTime();this.user=true};Strophe.TimedHandler.prototype={run:function(){this.lastCalled=new Date().getTime();return this.handler()},reset:function(){this.lastCalled=new Date().getTime()},toString:function(){return"{TimedHandler: "+this.handler+"("+this.period+")}"}};Strophe.Connection=function(service,options){this.service=service;this.options=options||{};var proto=this.options.protocol||"";if(service.indexOf("ws:")===0||service.indexOf("wss:")===0||proto.indexOf("ws")===0){this._proto=new Strophe.Websocket(this)}else{this._proto=new Strophe.Bosh(this)}this.jid="";this.domain=null;this.features=null;this._sasl_data={};this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._authentication={};this._idleTimeout=null;this._disconnectTimeout=null;this.do_authentication=true;this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this.paused=false;this._data=[];this._uniqueId=0;this._sasl_success_handler=null;this._sasl_failure_handler=null;this._sasl_challenge_handler=null;this.maxRetries=5;this._idleTimeout=setTimeout(this._onIdle.bind(this),100);for(var k in Strophe._connectionPlugins){if(Strophe._connectionPlugins.hasOwnProperty(k)){var ptype=Strophe._connectionPlugins[k];var F=function(){};F.prototype=ptype;this[k]=new F();this[k].init(this)}}};Strophe.Connection.prototype={reset:function(){this._proto._reset();this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._authentication={};this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this._requests=[];this._uniqueId=0},pause:function(){this.paused=true},resume:function(){this.paused=false},getUniqueId:function(suffix){if(typeof(suffix)=="string"||typeof(suffix)=="number"){return ++this._uniqueId+":"+suffix}else{return ++this._uniqueId+""}},connect:function(jid,pass,callback,wait,hold,route){this.jid=jid;this.authzid=Strophe.getBareJidFromJid(this.jid);this.authcid=Strophe.getNodeFromJid(this.jid);this.pass=pass;this.servtype="xmpp";this.connect_callback=callback;this.disconnecting=false;this.connected=false;this.authenticated=false;this.errors=0;this.domain=Strophe.getDomainFromJid(this.jid);this._changeConnectStatus(Strophe.Status.CONNECTING,null);this._proto._connect(wait,hold,route)},attach:function(jid,sid,rid,callback,wait,hold,wind){this._proto._attach(jid,sid,rid,callback,wait,hold,wind)},xmlInput:function(elem){return},xmlOutput:function(elem){return},rawInput:function(data){return},rawOutput:function(data){return},send:function(elem){if(elem===null){return}if(typeof(elem.sort)==="function"){for(var i=0;i0){hand=this.removeHandlers.pop();i=this.handlers.indexOf(hand);if(i>=0){this.handlers.splice(i,1)}}while(this.addHandlers.length>0){this.handlers.push(this.addHandlers.pop())}if(this.disconnecting&&this._proto._emptyQueue()){this._doDisconnect();return}var typ=elem.getAttribute("type");var cond,conflict;if(typ!==null&&typ=="terminate"){if(this.disconnecting){return}cond=elem.getAttribute("condition");conflict=elem.getElementsByTagName("conflict");if(cond!==null){if(cond=="remote-stream-error"&&conflict.length>0){cond="conflict"}this._changeConnectStatus(Strophe.Status.CONNFAIL,cond)}else{this._changeConnectStatus(Strophe.Status.CONNFAIL,"unknown")}this.disconnect("unknown stream-error");return}var that=this;Strophe.forEachChild(elem,null,function(child){var i,newList;newList=that.handlers;that.handlers=[];for(i=0;i0;if(!hasFeatures){hasFeatures=bodyWrap.getElementsByTagName("features").length>0}var mechanisms=bodyWrap.getElementsByTagName("mechanism");var matched=[];var i,mech,found_authentication=false;if(!hasFeatures){this._proto._no_auth_received(_callback);return}if(mechanisms.length>0){for(i=0;i0;found_authentication=this._authentication.legacy_auth||matched.length>0;if(!found_authentication){this._proto._no_auth_received(_callback);return}if(this.do_authentication!==false){this.authenticate(matched)}},authenticate:function(matched){var i;for(i=0;imatched[higher].prototype.priority){higher=j}}if(higher!=i){var swap=matched[i];matched[i]=matched[higher];matched[higher]=swap}}var mechanism_found=false;for(i=0;i0){condition="conflict"}this._changeConnectStatus(Strophe.Status.AUTHFAIL,condition);return false}var bind=elem.getElementsByTagName("bind");var jidNode;if(bind.length>0){jidNode=bind[0].getElementsByTagName("jid");if(jidNode.length>0){this.jid=Strophe.getText(jidNode[0]);if(this.do_session){this._addSysHandler(this._sasl_session_cb.bind(this),null,null,null,"_session_auth_2");this.send($iq({type:"set",id:"_session_auth_2"}).c("session",{xmlns:Strophe.NS.SESSION}).tree())}else{this.authenticated=true;this._changeConnectStatus(Strophe.Status.CONNECTED,null)}}}else{Strophe.info("SASL binding failed.");this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);return false}},_sasl_session_cb:function(elem){if(elem.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(Strophe.Status.CONNECTED,null)}else{if(elem.getAttribute("type")=="error"){Strophe.info("Session creation failed.");this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);return false}}return false},_sasl_failure_cb:function(elem){if(this._sasl_success_handler){this.deleteHandler(this._sasl_success_handler);this._sasl_success_handler=null}if(this._sasl_challenge_handler){this.deleteHandler(this._sasl_challenge_handler);this._sasl_challenge_handler=null}if(this._sasl_mechanism){this._sasl_mechanism.onFailure()}this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);return false},_auth2_cb:function(elem){if(elem.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(Strophe.Status.CONNECTED,null)}else{if(elem.getAttribute("type")=="error"){this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);this.disconnect("authentication failed")}}return false},_addSysTimedHandler:function(period,handler){var thand=new Strophe.TimedHandler(period,handler);thand.user=false;this.addTimeds.push(thand);return thand},_addSysHandler:function(handler,ns,name,type,id){var hand=new Strophe.Handler(handler,ns,name,type,id);hand.user=false;this.addHandlers.push(hand);return hand},_onDisconnectTimeout:function(){Strophe.info("_onDisconnectTimeout was called");this._proto._onDisconnectTimeout();this._doDisconnect();return false},_onIdle:function(){var i,thand,since,newList;while(this.addTimeds.length>0){this.timedHandlers.push(this.addTimeds.pop())}while(this.removeTimeds.length>0){thand=this.removeTimeds.pop();i=this.timedHandlers.indexOf(thand);if(i>=0){this.timedHandlers.splice(i,1)}}var now=new Date().getTime();newList=[];for(i=0;i0){cond="conflict"}this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,cond)}else{this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"unknown")}this._conn._doDisconnect();return Strophe.Status.CONNFAIL}if(!this.sid){this.sid=bodyWrap.getAttribute("sid")}var wind=bodyWrap.getAttribute("requests");if(wind){this.window=parseInt(wind,10)}var hold=bodyWrap.getAttribute("hold");if(hold){this.hold=parseInt(hold,10)}var wait=bodyWrap.getAttribute("wait");if(wait){this.wait=parseInt(wait,10)}},_disconnect:function(pres){this._sendTerminate(pres)},_doDisconnect:function(){this.sid=null;this.rid=Math.floor(Math.random()*4294967295)},_emptyQueue:function(){return this._requests.length===0},_hitError:function(reqStatus){this.errors++;Strophe.warn("request errored, status: "+reqStatus+", number of errors: "+this.errors);if(this.errors>4){this._onDisconnectTimeout()}},_no_auth_received:function(_callback){if(_callback){_callback=_callback.bind(this._conn)}else{_callback=this._conn._connect_cb.bind(this._conn)}var body=this._buildBody();this._requests.push(new Strophe.Request(body.tree(),this._onRequestStateChange.bind(this,_callback.bind(this._conn)),body.tree().getAttribute("rid")));this._throttledRequestHandler()},_onDisconnectTimeout:function(){var req;while(this._requests.length>0){req=this._requests.pop();req.abort=true;req.xhr.abort();req.xhr.onreadystatechange=function(){}}},_onIdle:function(){var data=this._conn._data;if(this._conn.authenticated&&this._requests.length===0&&data.length===0&&!this._conn.disconnecting){Strophe.info("no requests during idle cycle, sending blank request");data.push(null)}if(this._requests.length<2&&data.length>0&&!this._conn.paused){var body=this._buildBody();for(var i=0;i0){var time_elapsed=this._requests[0].age();if(this._requests[0].dead!==null){if(this._requests[0].timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait)){this._throttledRequestHandler()}}if(time_elapsed>Math.floor(Strophe.TIMEOUT*this.wait)){Strophe.warn("Request "+this._requests[0].id+" timed out, over "+Math.floor(Strophe.TIMEOUT*this.wait)+" seconds since last activity");this._throttledRequestHandler()}}},_onRequestStateChange:function(func,req){Strophe.debug("request id "+req.id+"."+req.sends+" state changed to "+req.xhr.readyState);if(req.abort){req.abort=false;return}var reqStatus;if(req.xhr.readyState==4){reqStatus=0;try{reqStatus=req.xhr.status}catch(e){}if(typeof(reqStatus)=="undefined"){reqStatus=0}if(this.disconnecting){if(reqStatus>=400){this._hitError(reqStatus);return}}var reqIs0=(this._requests[0]==req);var reqIs1=(this._requests[1]==req);if((reqStatus>0&&reqStatus<500)||req.sends>5){this._removeRequest(req);Strophe.debug("request id "+req.id+" should now be removed")}if(reqStatus==200){if(reqIs1||(reqIs0&&this._requests.length>0&&this._requests[0].age()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait))){this._restartRequest(0)}Strophe.debug("request id "+req.id+"."+req.sends+" got 200");func(req);this.errors=0}else{Strophe.error("request id "+req.id+"."+req.sends+" error "+reqStatus+" happened");if(reqStatus===0||(reqStatus>=400&&reqStatus<600)||reqStatus>=12000){this._hitError(reqStatus);if(reqStatus>=400&&reqStatus<500){this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING,null);this._conn._doDisconnect()}}}if(!((reqStatus>0&&reqStatus<500)||req.sends>5)){this._throttledRequestHandler()}}},_processRequest:function(i){var self=this;var req=this._requests[i];var reqStatus=-1;try{if(req.xhr.readyState==4){reqStatus=req.xhr.status}}catch(e){Strophe.error("caught an error in _requests["+i+"], reqStatus: "+reqStatus)}if(typeof(reqStatus)=="undefined"){reqStatus=-1}if(req.sends>this.maxRetries){this._onDisconnectTimeout();return}var time_elapsed=req.age();var primaryTimeout=(!isNaN(time_elapsed)&&time_elapsed>Math.floor(Strophe.TIMEOUT*this.wait));var secondaryTimeout=(req.dead!==null&&req.timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait));var requestCompletedWithServerError=(req.xhr.readyState==4&&(reqStatus<1||reqStatus>=500));if(primaryTimeout||secondaryTimeout||requestCompletedWithServerError){if(secondaryTimeout){Strophe.error("Request "+this._requests[i].id+" timed out (secondary), restarting")}req.abort=true;req.xhr.abort();req.xhr.onreadystatechange=function(){};this._requests[i]=new Strophe.Request(req.xmlData,req.origFunc,req.rid,req.sends);req=this._requests[i]}if(req.xhr.readyState===0){Strophe.debug("request id "+req.id+"."+req.sends+" posting");try{req.xhr.open("POST",this._conn.service,this._conn.options.sync?false:true)}catch(e2){Strophe.error("XHR open failed.");if(!this._conn.connected){this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"bad-service")}this._conn.disconnect();return}var sendFunc=function(){req.date=new Date();if(self._conn.options.customHeaders){var headers=self._conn.options.customHeaders;for(var header in headers){if(headers.hasOwnProperty(header)){req.xhr.setRequestHeader(header,headers[header])}}}req.xhr.send(req.data)};if(req.sends>1){var backoff=Math.min(Math.floor(Strophe.TIMEOUT*this.wait),Math.pow(req.sends,3))*1000;setTimeout(sendFunc,backoff)}else{sendFunc()}req.sends++;if(this._conn.xmlOutput!==Strophe.Connection.prototype.xmlOutput){if(req.xmlData.nodeName===this.strip&&req.xmlData.childNodes.length){this._conn.xmlOutput(req.xmlData.childNodes[0])}else{this._conn.xmlOutput(req.xmlData)}}if(this._conn.rawOutput!==Strophe.Connection.prototype.rawOutput){this._conn.rawOutput(req.data)}}else{Strophe.debug("_processRequest: "+(i===0?"first":"second")+" request has readyState of "+req.xhr.readyState)}},_removeRequest:function(req){Strophe.debug("removing request");var i;for(i=this._requests.length-1;i>=0;i--){if(req==this._requests[i]){this._requests.splice(i,1)}}req.xhr.onreadystatechange=function(){};this._throttledRequestHandler()},_restartRequest:function(i){var req=this._requests[i];if(req.dead===null){req.dead=new Date()}this._processRequest(i)},_reqToData:function(req){try{return req.getResponse()}catch(e){if(e!="parsererror"){throw e}this._conn.disconnect("strophe-parsererror")}},_sendTerminate:function(pres){Strophe.info("_sendTerminate was called");var body=this._buildBody().attrs({type:"terminate"});if(pres){body.cnode(pres.tree())}var req=new Strophe.Request(body.tree(),this._onRequestStateChange.bind(this,this._conn._dataRecv.bind(this._conn)),body.tree().getAttribute("rid"));this._requests.push(req);this._throttledRequestHandler()},_send:function(){clearTimeout(this._conn._idleTimeout);this._throttledRequestHandler();this._conn._idleTimeout=setTimeout(this._conn._onIdle.bind(this._conn),100)},_sendRestart:function(){this._throttledRequestHandler();clearTimeout(this._conn._idleTimeout)},_throttledRequestHandler:function(){if(!this._requests){Strophe.debug("_throttledRequestHandler called with undefined requests")}else{Strophe.debug("_throttledRequestHandler called with "+this._requests.length+" requests")}if(!this._requests||this._requests.length===0){return}if(this._requests.length>0){this._processRequest(0)}if(this._requests.length>1&&Math.abs(this._requests[0].rid-this._requests[1].rid)\s*)*/,"");if(data===""){return}data=message.data.replace(//,"");var streamStart=new DOMParser().parseFromString(data,"text/xml").documentElement;this._conn.xmlInput(streamStart);this._conn.rawInput(message.data);if(this._handleStreamStart(streamStart)){this._connect_cb(streamStart);this.streamStart=message.data.replace(/^$/,"")}}else{if(message.data===""){this._conn.rawInput(message.data);this._conn.xmlInput(document.createElement("stream:stream"));this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Received closing stream");this._conn._doDisconnect();return}else{var string=this._streamWrap(message.data);var elem=new DOMParser().parseFromString(string,"text/xml").documentElement;this.socket.onmessage=this._onMessage.bind(this);this._conn._connect_cb(elem,null,message.data)}}},_disconnect:function(pres){if(this.socket.readyState!==WebSocket.CLOSED){if(pres){this._conn.send(pres)}var close="";this._conn.xmlOutput(document.createElement("stream:stream"));this._conn.rawOutput(close);try{this.socket.send(close)}catch(e){Strophe.info("Couldn't send closing stream tag.")}}this._conn._doDisconnect()},_doDisconnect:function(){Strophe.info("WebSockets _doDisconnect was called");this._closeSocket()},_streamWrap:function(stanza){return this.streamStart+stanza+""},_closeSocket:function(){if(this.socket){try{this.socket.close()}catch(e){}}this.socket=null},_emptyQueue:function(){return true},_onClose:function(){if(this._conn.connected&&!this._conn.disconnecting){Strophe.error("Websocket closed unexcectedly");this._conn._doDisconnect()}else{Strophe.info("Websocket closed")}},_no_auth_received:function(_callback){Strophe.error("Server did not send any auth methods");this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Server did not send any auth methods");if(_callback){_callback=_callback.bind(this._conn);_callback()}this._conn._doDisconnect()},_onDisconnectTimeout:function(){},_onError:function(error){Strophe.error("Websocket error "+error);this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"The WebSocket connection could not be established was disconnected.");this._disconnect()},_onIdle:function(){var data=this._conn._data;if(data.length>0&&!this._conn.paused){for(var i=0;i"){var close="";this._conn.rawInput(close);this._conn.xmlInput(document.createElement("stream:stream"));if(!this._conn.disconnecting){this._conn._doDisconnect()}return}else{if(message.data.search("/,"");elem=new DOMParser().parseFromString(data,"text/xml").documentElement;if(!this._handleStreamStart(elem)){return}}else{data=this._streamWrap(message.data);elem=new DOMParser().parseFromString(data,"text/xml").documentElement}}if(this._check_streamerror(elem,Strophe.Status.ERROR)){return}if(this._conn.disconnecting&&elem.firstChild.nodeName==="presence"&&elem.firstChild.getAttribute("type")==="unavailable"){this._conn.xmlInput(elem);this._conn.rawInput(Strophe.serialize(elem));return}this._conn._dataRecv(elem,message.data)},_onOpen:function(){Strophe.info("Websocket open");var start=this._buildStream();this._conn.xmlOutput(start.tree());var startString=this._removeClosingTag(start);this._conn.rawOutput(startString);this.socket.send(startString)},_removeClosingTag:function(elem){var string=Strophe.serialize(elem);string=string.replace(/<(stream:stream .*[^\/])\/>$/,"<$1>");return string},_reqToData:function(stanza){return stanza},_send:function(){this._conn.flush()},_sendRestart:function(){clearTimeout(this._conn._idleTimeout);this._conn._onIdle.bind(this._conn)()}}; \ No newline at end of file +var Base64=(function(){var keyStr="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var obj={encode:function(input){var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;do{chr1=input.charCodeAt(i++);chr2=input.charCodeAt(i++);chr3=input.charCodeAt(i++);enc1=chr1>>2;enc2=((chr1&3)<<4)|(chr2>>4);enc3=((chr2&15)<<2)|(chr3>>6);enc4=chr3&63;if(isNaN(chr2)){enc3=enc4=64}else{if(isNaN(chr3)){enc4=64}}output=output+keyStr.charAt(enc1)+keyStr.charAt(enc2)+keyStr.charAt(enc3)+keyStr.charAt(enc4)}while(i>4);chr2=((enc2&15)<<4)|(enc3>>2);chr3=((enc3&3)<<6)|enc4;output=output+String.fromCharCode(chr1);if(enc3!=64){output=output+String.fromCharCode(chr2)}if(enc4!=64){output=output+String.fromCharCode(chr3)}}while(i>5]|=128<<(24-len%32);x[((len+64>>9)<<4)+15]=len;var w=new Array(80);var a=1732584193;var b=-271733879;var c=-1732584194;var d=271733878;var e=-1009589776;var i,j,t,olda,oldb,oldc,oldd,olde;for(i=0;i16){bkey=core_sha1(bkey,key.length*8)}var ipad=new Array(16),opad=new Array(16);for(var i=0;i<16;i++){ipad[i]=bkey[i]^909522486;opad[i]=bkey[i]^1549556828}var hash=core_sha1(ipad.concat(str2binb(data)),512+data.length*8);return core_sha1(opad.concat(hash),512+160)}function safe_add(x,y){var lsw=(x&65535)+(y&65535);var msw=(x>>16)+(y>>16)+(lsw>>16);return(msw<<16)|(lsw&65535)}function rol(num,cnt){return(num<>>(32-cnt))}function str2binb(str){var bin=[];var mask=255;for(var i=0;i>5]|=(str.charCodeAt(i/8)&mask)<<(24-i%32)}return bin}function binb2str(bin){var str="";var mask=255;for(var i=0;i>5]>>>(24-i%32))&mask)}return str}function binb2b64(binarray){var tab="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var str="";var triplet,j;for(var i=0;i>2]>>8*(3-i%4))&255)<<16)|(((binarray[i+1>>2]>>8*(3-(i+1)%4))&255)<<8)|((binarray[i+2>>2]>>8*(3-(i+2)%4))&255);for(j=0;j<4;j++){if(i*8+j*6>binarray.length*32){str+="="}else{str+=tab.charAt((triplet>>6*(3-j))&63)}}}return str}var MD5=(function(){var safe_add=function(x,y){var lsw=(x&65535)+(y&65535);var msw=(x>>16)+(y>>16)+(lsw>>16);return(msw<<16)|(lsw&65535)};var bit_rol=function(num,cnt){return(num<>>(32-cnt))};var str2binl=function(str){var bin=[];for(var i=0;i>5]|=(str.charCodeAt(i/8)&255)<<(i%32)}return bin};var binl2str=function(bin){var str="";for(var i=0;i>5]>>>(i%32))&255)}return str};var binl2hex=function(binarray){var hex_tab="0123456789abcdef";var str="";for(var i=0;i>2]>>((i%4)*8+4))&15)+hex_tab.charAt((binarray[i>>2]>>((i%4)*8))&15)}return str};var md5_cmn=function(q,a,b,x,s,t){return safe_add(bit_rol(safe_add(safe_add(a,q),safe_add(x,t)),s),b)};var md5_ff=function(a,b,c,d,x,s,t){return md5_cmn((b&c)|((~b)&d),a,b,x,s,t)};var md5_gg=function(a,b,c,d,x,s,t){return md5_cmn((b&d)|(c&(~d)),a,b,x,s,t)};var md5_hh=function(a,b,c,d,x,s,t){return md5_cmn(b^c^d,a,b,x,s,t)};var md5_ii=function(a,b,c,d,x,s,t){return md5_cmn(c^(b|(~d)),a,b,x,s,t)};var core_md5=function(x,len){x[len>>5]|=128<<((len)%32);x[(((len+64)>>>9)<<4)+14]=len;var a=1732584193;var b=-271733879;var c=-1732584194;var d=271733878;var olda,oldb,oldc,oldd;for(var i=0;i0){for(var i=0;i/g,">");text=text.replace(/'/g,"'");text=text.replace(/"/g,""");return text},xmlTextNode:function(text){return Strophe.xmlGenerator().createTextNode(text)},xmlHtmlNode:function(html){var node;if(window.DOMParser){var parser=new DOMParser();node=parser.parseFromString(html,"text/xml")}else{node=new ActiveXObject("Microsoft.XMLDOM");node.async="false";node.loadXML(html)}return node},getText:function(elem){if(!elem){return null}var str="";if(elem.childNodes.length===0&&elem.nodeType==Strophe.ElementType.TEXT){str+=elem.nodeValue}for(var i=0;i0){value=css.join("; ");el.setAttribute(attribute,value)}}else{el.setAttribute(attribute,value)}}for(i=0;i/g,"\\3e").replace(/@/g,"\\40")},unescapeNode:function(node){return node.replace(/\\20/g," ").replace(/\\22/g,'"').replace(/\\26/g,"&").replace(/\\27/g,"'").replace(/\\2f/g,"/").replace(/\\3a/g,":").replace(/\\3c/g,"<").replace(/\\3e/g,">").replace(/\\40/g,"@").replace(/\\5c/g,"\\")},getNodeFromJid:function(jid){if(jid.indexOf("@")<0){return null}return jid.split("@")[0]},getDomainFromJid:function(jid){var bare=Strophe.getBareJidFromJid(jid);if(bare.indexOf("@")<0){return bare}else{var parts=bare.split("@");parts.splice(0,1);return parts.join("@")}},getResourceFromJid:function(jid){var s=jid.split("/");if(s.length<2){return null}s.splice(0,1);return s.join("/")},getBareJidFromJid:function(jid){return jid?jid.split("/")[0]:null},log:function(level,msg){return},debug:function(msg){this.log(this.LogLevel.DEBUG,msg)},info:function(msg){this.log(this.LogLevel.INFO,msg)},warn:function(msg){this.log(this.LogLevel.WARN,msg)},error:function(msg){this.log(this.LogLevel.ERROR,msg)},fatal:function(msg){this.log(this.LogLevel.FATAL,msg)},serialize:function(elem){var result;if(!elem){return null}if(typeof(elem.tree)==="function"){elem=elem.tree()}var nodeName=elem.nodeName;var i,child;if(elem.getAttribute("_realname")){nodeName=elem.getAttribute("_realname")}result="<"+nodeName;for(i=0;i/g,">").replace(/0){result+=">";for(i=0;i"}}result+=""}else{result+="/>"}return result},_requestId:0,_connectionPlugins:{},addConnectionPlugin:function(name,ptype){Strophe._connectionPlugins[name]=ptype}};Strophe.Builder=function(name,attrs){if(name=="presence"||name=="message"||name=="iq"){if(attrs&&!attrs.xmlns){attrs.xmlns=Strophe.NS.CLIENT}else{if(!attrs){attrs={xmlns:Strophe.NS.CLIENT}}}}this.nodeTree=Strophe.xmlElement(name,attrs);this.node=this.nodeTree};Strophe.Builder.prototype={tree:function(){return this.nodeTree},toString:function(){return Strophe.serialize(this.nodeTree)},up:function(){this.node=this.node.parentNode;return this},attrs:function(moreattrs){for(var k in moreattrs){if(moreattrs.hasOwnProperty(k)){this.node.setAttribute(k,moreattrs[k])}}return this},c:function(name,attrs,text){var child=Strophe.xmlElement(name,attrs,text);this.node.appendChild(child);if(!text){this.node=child}return this},cnode:function(elem){var impNode;var xmlGen=Strophe.xmlGenerator();try{impNode=(xmlGen.importNode!==undefined)}catch(e){impNode=false}var newElem=impNode?xmlGen.importNode(elem,true):Strophe.copyElement(elem);this.node.appendChild(newElem);this.node=newElem;return this},t:function(text){var child=Strophe.xmlTextNode(text);this.node.appendChild(child);return this},h:function(html){var fragment=document.createElement("body");fragment.innerHTML=html;var xhtml=Strophe.createHtml(fragment);while(xhtml.childNodes.length>0){this.node.appendChild(xhtml.childNodes[0])}return this}};Strophe.Handler=function(handler,ns,name,type,id,from,options){this.handler=handler;this.ns=ns;this.name=name;this.type=type;this.id=id;this.options=options||{matchBare:false};if(!this.options.matchBare){this.options.matchBare=false}if(this.options.matchBare){this.from=from?Strophe.getBareJidFromJid(from):null}else{this.from=from}this.user=true};Strophe.Handler.prototype={isMatch:function(elem){var nsMatch;var from=null;if(this.options.matchBare){from=Strophe.getBareJidFromJid(elem.getAttribute("from"))}else{from=elem.getAttribute("from")}nsMatch=false;if(!this.ns){nsMatch=true}else{var that=this;Strophe.forEachChild(elem,null,function(elem){if(elem.getAttribute("xmlns")==that.ns){nsMatch=true}});nsMatch=nsMatch||elem.getAttribute("xmlns")==this.ns}if(nsMatch&&(!this.name||Strophe.isTagEqual(elem,this.name))&&(!this.type||elem.getAttribute("type")==this.type)&&(!this.id||elem.getAttribute("id")==this.id)&&(!this.from||from==this.from)){return true}return false},run:function(elem){var result=null;try{result=this.handler(elem)}catch(e){if(e.sourceURL){Strophe.fatal("error: "+this.handler+" "+e.sourceURL+":"+e.line+" - "+e.name+": "+e.message)}else{if(e.fileName){if(typeof(console)!="undefined"){console.trace();console.error(this.handler," - error - ",e,e.message)}Strophe.fatal("error: "+this.handler+" "+e.fileName+":"+e.lineNumber+" - "+e.name+": "+e.message)}else{Strophe.fatal("error: "+e.message+"\n"+e.stack)}}throw e}return result},toString:function(){return"{Handler: "+this.handler+"("+this.name+","+this.id+","+this.ns+")}"}};Strophe.TimedHandler=function(period,handler){this.period=period;this.handler=handler;this.lastCalled=new Date().getTime();this.user=true};Strophe.TimedHandler.prototype={run:function(){this.lastCalled=new Date().getTime();return this.handler()},reset:function(){this.lastCalled=new Date().getTime()},toString:function(){return"{TimedHandler: "+this.handler+"("+this.period+")}"}};Strophe.Connection=function(service,options){this.service=service;this.options=options||{};var proto=this.options.protocol||"";if(service.indexOf("ws:")===0||service.indexOf("wss:")===0||proto.indexOf("ws")===0){this._proto=new Strophe.Websocket(this)}else{this._proto=new Strophe.Bosh(this)}this.jid="";this.domain=null;this.features=null;this._sasl_data={};this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._authentication={};this._idleTimeout=null;this._disconnectTimeout=null;this.do_authentication=true;this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this.paused=false;this._data=[];this._uniqueId=0;this._sasl_success_handler=null;this._sasl_failure_handler=null;this._sasl_challenge_handler=null;this.maxRetries=5;this._idleTimeout=setTimeout(this._onIdle.bind(this),100);for(var k in Strophe._connectionPlugins){if(Strophe._connectionPlugins.hasOwnProperty(k)){var ptype=Strophe._connectionPlugins[k];var F=function(){};F.prototype=ptype;this[k]=new F();this[k].init(this)}}};Strophe.Connection.prototype={reset:function(){this._proto._reset();this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._authentication={};this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this._requests=[];this._uniqueId=0},pause:function(){this.paused=true},resume:function(){this.paused=false},getUniqueId:function(suffix){if(typeof(suffix)=="string"||typeof(suffix)=="number"){return ++this._uniqueId+":"+suffix}else{return ++this._uniqueId+""}},connect:function(jid,pass,callback,wait,hold,route){this.jid=jid;this.authzid=Strophe.getBareJidFromJid(this.jid);this.authcid=Strophe.getNodeFromJid(this.jid);this.pass=pass;this.servtype="xmpp";this.connect_callback=callback;this.disconnecting=false;this.connected=false;this.authenticated=false;this.errors=0;this.domain=Strophe.getDomainFromJid(this.jid);this._changeConnectStatus(Strophe.Status.CONNECTING,null);this._proto._connect(wait,hold,route)},attach:function(jid,sid,rid,callback,wait,hold,wind){this._proto._attach(jid,sid,rid,callback,wait,hold,wind)},xmlInput:function(elem){return},xmlOutput:function(elem){return},rawInput:function(data){return},rawOutput:function(data){return},send:function(elem){if(elem===null){return}if(typeof(elem.sort)==="function"){for(var i=0;i0){hand=this.removeHandlers.pop();i=this.handlers.indexOf(hand);if(i>=0){this.handlers.splice(i,1)}}while(this.addHandlers.length>0){this.handlers.push(this.addHandlers.pop())}if(this.disconnecting&&this._proto._emptyQueue()){this._doDisconnect();return}var typ=elem.getAttribute("type");var cond,conflict;if(typ!==null&&typ=="terminate"){if(this.disconnecting){return}cond=elem.getAttribute("condition");conflict=elem.getElementsByTagName("conflict");if(cond!==null){if(cond=="remote-stream-error"&&conflict.length>0){cond="conflict"}this._changeConnectStatus(Strophe.Status.CONNFAIL,cond)}else{this._changeConnectStatus(Strophe.Status.CONNFAIL,"unknown")}this.disconnect("unknown stream-error");return}var that=this;Strophe.forEachChild(elem,null,function(child){var i,newList;newList=that.handlers;that.handlers=[];for(i=0;i0;if(!hasFeatures){hasFeatures=bodyWrap.getElementsByTagName("features").length>0}var mechanisms=bodyWrap.getElementsByTagName("mechanism");var matched=[];var i,mech,found_authentication=false;if(!hasFeatures){this._proto._no_auth_received(_callback);return}if(mechanisms.length>0){for(i=0;i0;found_authentication=this._authentication.legacy_auth||matched.length>0;if(!found_authentication){this._proto._no_auth_received(_callback);return}if(this.do_authentication!==false){this.authenticate(matched)}},authenticate:function(matched){var i;for(i=0;imatched[higher].prototype.priority){higher=j}}if(higher!=i){var swap=matched[i];matched[i]=matched[higher];matched[higher]=swap}}var mechanism_found=false;for(i=0;i0){condition="conflict"}this._changeConnectStatus(Strophe.Status.AUTHFAIL,condition);return false}var bind=elem.getElementsByTagName("bind");var jidNode;if(bind.length>0){jidNode=bind[0].getElementsByTagName("jid");if(jidNode.length>0){this.jid=Strophe.getText(jidNode[0]);if(this.do_session){this._addSysHandler(this._sasl_session_cb.bind(this),null,null,null,"_session_auth_2");this.send($iq({type:"set",id:"_session_auth_2"}).c("session",{xmlns:Strophe.NS.SESSION}).tree())}else{this.authenticated=true;this._changeConnectStatus(Strophe.Status.CONNECTED,null)}}}else{Strophe.info("SASL binding failed.");this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);return false}},_sasl_session_cb:function(elem){if(elem.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(Strophe.Status.CONNECTED,null)}else{if(elem.getAttribute("type")=="error"){Strophe.info("Session creation failed.");this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);return false}}return false},_sasl_failure_cb:function(elem){if(this._sasl_success_handler){this.deleteHandler(this._sasl_success_handler);this._sasl_success_handler=null}if(this._sasl_challenge_handler){this.deleteHandler(this._sasl_challenge_handler);this._sasl_challenge_handler=null}if(this._sasl_mechanism){this._sasl_mechanism.onFailure()}this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);return false},_auth2_cb:function(elem){if(elem.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(Strophe.Status.CONNECTED,null)}else{if(elem.getAttribute("type")=="error"){this._changeConnectStatus(Strophe.Status.AUTHFAIL,null);this.disconnect("authentication failed")}}return false},_addSysTimedHandler:function(period,handler){var thand=new Strophe.TimedHandler(period,handler);thand.user=false;this.addTimeds.push(thand);return thand},_addSysHandler:function(handler,ns,name,type,id){var hand=new Strophe.Handler(handler,ns,name,type,id);hand.user=false;this.addHandlers.push(hand);return hand},_onDisconnectTimeout:function(){Strophe.info("_onDisconnectTimeout was called");this._proto._onDisconnectTimeout();this._doDisconnect();return false},_onIdle:function(){var i,thand,since,newList;while(this.addTimeds.length>0){this.timedHandlers.push(this.addTimeds.pop())}while(this.removeTimeds.length>0){thand=this.removeTimeds.pop();i=this.timedHandlers.indexOf(thand);if(i>=0){this.timedHandlers.splice(i,1)}}var now=new Date().getTime();newList=[];for(i=0;i0){cond="conflict"}this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,cond)}else{this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"unknown")}this._conn._doDisconnect();return Strophe.Status.CONNFAIL}if(!this.sid){this.sid=bodyWrap.getAttribute("sid")}var wind=bodyWrap.getAttribute("requests");if(wind){this.window=parseInt(wind,10)}var hold=bodyWrap.getAttribute("hold");if(hold){this.hold=parseInt(hold,10)}var wait=bodyWrap.getAttribute("wait");if(wait){this.wait=parseInt(wait,10)}},_disconnect:function(pres){this._sendTerminate(pres)},_doDisconnect:function(){this.sid=null;this.rid=Math.floor(Math.random()*4294967295)},_emptyQueue:function(){return this._requests.length===0},_hitError:function(reqStatus){this.errors++;Strophe.warn("request errored, status: "+reqStatus+", number of errors: "+this.errors);if(this.errors>4){this._onDisconnectTimeout()}},_no_auth_received:function(_callback){if(_callback){_callback=_callback.bind(this._conn)}else{_callback=this._conn._connect_cb.bind(this._conn)}var body=this._buildBody();this._requests.push(new Strophe.Request(body.tree(),this._onRequestStateChange.bind(this,_callback.bind(this._conn)),body.tree().getAttribute("rid")));this._throttledRequestHandler()},_onDisconnectTimeout:function(){var req;while(this._requests.length>0){req=this._requests.pop();req.abort=true;req.xhr.abort();req.xhr.onreadystatechange=function(){}}},_onIdle:function(){var data=this._conn._data;if(this._conn.authenticated&&this._requests.length===0&&data.length===0&&!this._conn.disconnecting){Strophe.info("no requests during idle cycle, sending blank request");data.push(null)}if(this._requests.length<2&&data.length>0&&!this._conn.paused){var body=this._buildBody();for(var i=0;i0){var time_elapsed=this._requests[0].age();if(this._requests[0].dead!==null){if(this._requests[0].timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait)){this._throttledRequestHandler()}}if(time_elapsed>Math.floor(Strophe.TIMEOUT*this.wait)){Strophe.warn("Request "+this._requests[0].id+" timed out, over "+Math.floor(Strophe.TIMEOUT*this.wait)+" seconds since last activity");this._throttledRequestHandler()}}},_onRequestStateChange:function(func,req){Strophe.debug("request id "+req.id+"."+req.sends+" state changed to "+req.xhr.readyState);if(req.abort){req.abort=false;return}var reqStatus;if(req.xhr.readyState==4){reqStatus=0;try{reqStatus=req.xhr.status}catch(e){}if(typeof(reqStatus)=="undefined"){reqStatus=0}if(this.disconnecting){if(reqStatus>=400){this._hitError(reqStatus);return}}var reqIs0=(this._requests[0]==req);var reqIs1=(this._requests[1]==req);if((reqStatus>0&&reqStatus<500)||req.sends>5){this._removeRequest(req);Strophe.debug("request id "+req.id+" should now be removed")}if(reqStatus==200){if(reqIs1||(reqIs0&&this._requests.length>0&&this._requests[0].age()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait))){this._restartRequest(0)}Strophe.debug("request id "+req.id+"."+req.sends+" got 200");func(req);this.errors=0}else{Strophe.error("request id "+req.id+"."+req.sends+" error "+reqStatus+" happened");if(reqStatus===0||(reqStatus>=400&&reqStatus<600)||reqStatus>=12000){this._hitError(reqStatus);if(reqStatus>=400&&reqStatus<500){this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING,null);this._conn._doDisconnect()}}}if(!((reqStatus>0&&reqStatus<500)||req.sends>5)){this._throttledRequestHandler()}}},_processRequest:function(i){var self=this;var req=this._requests[i];var reqStatus=-1;try{if(req.xhr.readyState==4){reqStatus=req.xhr.status}}catch(e){Strophe.error("caught an error in _requests["+i+"], reqStatus: "+reqStatus)}if(typeof(reqStatus)=="undefined"){reqStatus=-1}if(req.sends>this.maxRetries){this._onDisconnectTimeout();return}var time_elapsed=req.age();var primaryTimeout=(!isNaN(time_elapsed)&&time_elapsed>Math.floor(Strophe.TIMEOUT*this.wait));var secondaryTimeout=(req.dead!==null&&req.timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait));var requestCompletedWithServerError=(req.xhr.readyState==4&&(reqStatus<1||reqStatus>=500));if(primaryTimeout||secondaryTimeout||requestCompletedWithServerError){if(secondaryTimeout){Strophe.error("Request "+this._requests[i].id+" timed out (secondary), restarting")}req.abort=true;req.xhr.abort();req.xhr.onreadystatechange=function(){};this._requests[i]=new Strophe.Request(req.xmlData,req.origFunc,req.rid,req.sends);req=this._requests[i]}if(req.xhr.readyState===0){Strophe.debug("request id "+req.id+"."+req.sends+" posting");try{req.xhr.open("POST",this._conn.service,this._conn.options.sync?false:true)}catch(e2){Strophe.error("XHR open failed.");if(!this._conn.connected){this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"bad-service")}this._conn.disconnect();return}var sendFunc=function(){req.date=new Date();if(self._conn.options.customHeaders){var headers=self._conn.options.customHeaders;for(var header in headers){if(headers.hasOwnProperty(header)){req.xhr.setRequestHeader(header,headers[header])}}}req.xhr.send(req.data)};if(req.sends>1){var backoff=Math.min(Math.floor(Strophe.TIMEOUT*this.wait),Math.pow(req.sends,3))*1000;setTimeout(sendFunc,backoff)}else{sendFunc()}req.sends++;if(this._conn.xmlOutput!==Strophe.Connection.prototype.xmlOutput){if(req.xmlData.nodeName===this.strip&&req.xmlData.childNodes.length){this._conn.xmlOutput(req.xmlData.childNodes[0])}else{this._conn.xmlOutput(req.xmlData)}}if(this._conn.rawOutput!==Strophe.Connection.prototype.rawOutput){this._conn.rawOutput(req.data)}}else{Strophe.debug("_processRequest: "+(i===0?"first":"second")+" request has readyState of "+req.xhr.readyState)}},_removeRequest:function(req){Strophe.debug("removing request");var i;for(i=this._requests.length-1;i>=0;i--){if(req==this._requests[i]){this._requests.splice(i,1)}}req.xhr.onreadystatechange=function(){};this._throttledRequestHandler()},_restartRequest:function(i){var req=this._requests[i];if(req.dead===null){req.dead=new Date()}this._processRequest(i)},_reqToData:function(req){try{return req.getResponse()}catch(e){if(e!="parsererror"){throw e}this._conn.disconnect("strophe-parsererror")}},_sendTerminate:function(pres){Strophe.info("_sendTerminate was called");var body=this._buildBody().attrs({type:"terminate"});if(pres){body.cnode(pres.tree())}var req=new Strophe.Request(body.tree(),this._onRequestStateChange.bind(this,this._conn._dataRecv.bind(this._conn)),body.tree().getAttribute("rid"));this._requests.push(req);this._throttledRequestHandler()},_send:function(){clearTimeout(this._conn._idleTimeout);this._throttledRequestHandler();this._conn._idleTimeout=setTimeout(this._conn._onIdle.bind(this._conn),100)},_sendRestart:function(){this._throttledRequestHandler();clearTimeout(this._conn._idleTimeout)},_throttledRequestHandler:function(){if(!this._requests){Strophe.debug("_throttledRequestHandler called with undefined requests")}else{Strophe.debug("_throttledRequestHandler called with "+this._requests.length+" requests")}if(!this._requests||this._requests.length===0){return}if(this._requests.length>0){this._processRequest(0)}if(this._requests.length>1&&Math.abs(this._requests[0].rid-this._requests[1].rid)\s*)*/,"");if(data===""){return}data=message.data.replace(//,"");var streamStart=new DOMParser().parseFromString(data,"text/xml").documentElement;this._conn.xmlInput(streamStart);this._conn.rawInput(message.data);if(this._handleStreamStart(streamStart)){this._connect_cb(streamStart);this.streamStart=message.data.replace(/^$/,"")}}else{if(message.data===""){this._conn.rawInput(message.data);this._conn.xmlInput(document.createElement("stream:stream"));this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Received closing stream");this._conn._doDisconnect();return}else{var string=this._streamWrap(message.data);var elem=new DOMParser().parseFromString(string,"text/xml").documentElement;this.socket.onmessage=this._onMessage.bind(this);this._conn._connect_cb(elem,null,message.data)}}},_disconnect:function(pres){if(this.socket.readyState!==WebSocket.CLOSED){if(pres){this._conn.send(pres)}var close="";this._conn.xmlOutput(document.createElement("stream:stream"));this._conn.rawOutput(close);try{this.socket.send(close)}catch(e){Strophe.info("Couldn't send closing stream tag.")}}this._conn._doDisconnect()},_doDisconnect:function(){Strophe.info("WebSockets _doDisconnect was called");this._closeSocket()},_streamWrap:function(stanza){return this.streamStart+stanza+""},_closeSocket:function(){if(this.socket){try{this.socket.close()}catch(e){}}this.socket=null},_emptyQueue:function(){return true},_onClose:function(){if(this._conn.connected&&!this._conn.disconnecting){Strophe.error("Websocket closed unexcectedly");this._conn._doDisconnect()}else{Strophe.info("Websocket closed")}},_no_auth_received:function(_callback){Strophe.error("Server did not send any auth methods");this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Server did not send any auth methods");if(_callback){_callback=_callback.bind(this._conn);_callback()}this._conn._doDisconnect()},_onDisconnectTimeout:function(){},_onError:function(error){Strophe.error("Websocket error "+error);this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"The WebSocket connection could not be established was disconnected.");this._disconnect()},_onIdle:function(){var data=this._conn._data;if(data.length>0&&!this._conn.paused){for(var i=0;i"){var close="";this._conn.rawInput(close);this._conn.xmlInput(document.createElement("stream:stream"));if(!this._conn.disconnecting){this._conn._doDisconnect()}return}else{if(message.data.search("/,"");elem=new DOMParser().parseFromString(data,"text/xml").documentElement;if(!this._handleStreamStart(elem)){return}}else{data=this._streamWrap(message.data);elem=new DOMParser().parseFromString(data,"text/xml").documentElement}}if(this._check_streamerror(elem,Strophe.Status.ERROR)){return}if(this._conn.disconnecting&&elem.firstChild.nodeName==="presence"&&elem.firstChild.getAttribute("type")==="unavailable"){this._conn.xmlInput(elem);this._conn.rawInput(Strophe.serialize(elem));return}this._conn._dataRecv(elem,message.data)},_onOpen:function(){Strophe.info("Websocket open");var start=this._buildStream();this._conn.xmlOutput(start.tree());var startString=this._removeClosingTag(start);this._conn.rawOutput(startString);this.socket.send(startString)},_removeClosingTag:function(elem){var string=Strophe.serialize(elem);string=string.replace(/<(stream:stream .*[^\/])\/>$/,"<$1>");return string},_reqToData:function(stanza){return stanza},_send:function(){this._conn.flush()},_sendRestart:function(){clearTimeout(this._conn._idleTimeout);this._conn._onIdle.bind(this._conn)()}}; \ No newline at end of file diff --git a/muc.js b/muc.js index d131e3da8c..d61bb2153a 100644 --- a/muc.js +++ b/muc.js @@ -112,7 +112,7 @@ Strophe.addConnectionPlugin('emuc', { var create = $iq({type: 'set', to: this.roomjid}) .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}) .c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - this.connection.send(create); // fire away + this.connection.sendIQ(create); // fire away } // Parse roles. diff --git a/rtp_sts.js b/rtp_sts.js index 27270ad081..654109d454 100644 --- a/rtp_sts.js +++ b/rtp_sts.js @@ -80,7 +80,14 @@ PeerStats.prototype.setSsrcResolution = function (ssrc, resolution) */ PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate) { - this.ssrc2bitrate[ssrc] = bitrate; + if(this.ssrc2bitrate[ssrc]) + { + this.ssrc2bitrate[ssrc].download += bitrate.download; + this.ssrc2bitrate[ssrc].upload += bitrate.upload; + } + else { + this.ssrc2bitrate[ssrc] = bitrate; + } }; /** @@ -103,6 +110,7 @@ PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel) */ PeerStats.transport = []; + /** * StatsCollector registers for stats updates of given * peerconnection in given interval. On each update particular @@ -210,7 +218,15 @@ StatsCollector.prototype.start = function () self.peerconnection.getStats( function (report) { - var results = report.result(); + var results = null; + if(!report || !report.result || typeof report.result != 'function') + { + results = report; + } + else + { + results = report.result(); + } //console.error("Got interval report", results); self.currentAudioLevelsReport = results; self.processAudioLevelReport(); @@ -229,10 +245,28 @@ StatsCollector.prototype.start = function () self.peerconnection.getStats( function (report) { - var results = report.result(); + var results = null; + if(!report || !report.result || typeof report.result != 'function') + { + //firefox + results = report; + } + else + { + //chrome + results = report.result(); + } //console.error("Got interval report", results); self.currentStatsReport = results; - self.processStatsReport(); + try + { + self.processStatsReport(); + } + catch(e) + { + console.error("Unsupported key:" + e); + } + self.baselineStatsReport = self.currentStatsReport; }, self.errorCallback @@ -318,6 +352,36 @@ StatsCollector.prototype.logStats = function () { this.statsToBeLogged.stats = {}; this.statsToBeLogged.timestamps = []; }; +var keyMap = { + "firefox": { + "ssrc": "ssrc", + "packetsReceived": "packetsReceived", + "packetsLost": "packetsLost", + "packetsSent": "packetsSent", + "bytesReceived": "bytesReceived", + "bytesSent": "bytesSent" + }, + "chrome": { + "receiveBandwidth": "googAvailableReceiveBandwidth", + "sendBandwidth": "googAvailableSendBandwidth", + "remoteAddress": "googRemoteAddress", + "transportType": "googTransportType", + "localAddress": "googLocalAddress", + "activeConnection": "googActiveConnection", + "ssrc": "ssrc", + "packetsReceived": "packetsReceived", + "packetsSent": "packetsSent", + "packetsLost": "packetsLost", + "bytesReceived": "bytesReceived", + "bytesSent": "bytesSent", + "googFrameHeightReceived": "googFrameHeightReceived", + "googFrameWidthReceived": "googFrameWidthReceived", + "googFrameHeightSent": "googFrameHeightSent", + "googFrameWidthSent": "googFrameWidthSent", + "audioInputLevel": "audioInputLevel", + "audioOutputLevel": "audioOutputLevel" + } +}; /** * Stats processing logic. @@ -329,23 +393,29 @@ StatsCollector.prototype.processStatsReport = function () { for (var idx in this.currentStatsReport) { var now = this.currentStatsReport[idx]; - if (now.stat('googAvailableReceiveBandwidth') || - now.stat('googAvailableSendBandwidth')) - { - PeerStats.bandwidth = { - "download": Math.round( - (now.stat('googAvailableReceiveBandwidth')) / 1000), - "upload": Math.round( - (now.stat('googAvailableSendBandwidth')) / 1000) - }; + try { + if (getStatValue(now, 'receiveBandwidth') || + getStatValue(now, 'sendBandwidth')) { + PeerStats.bandwidth = { + "download": Math.round( + (getStatValue(now, 'receiveBandwidth')) / 1000), + "upload": Math.round( + (getStatValue(now, 'sendBandwidth')) / 1000) + }; + } } + catch(e){/*not supported*/} if(now.type == 'googCandidatePair') { - var ip = now.stat('googRemoteAddress'); - var type = now.stat("googTransportType"); - var localIP = now.stat("googLocalAddress"); - var active = now.stat("googActiveConnection"); + var ip, type, localIP, active; + try { + ip = getStatValue(now, 'remoteAddress'); + type = getStatValue(now, "transportType"); + localIP = getStatValue(now, "localAddress"); + active = getStatValue(now, "activeConnection"); + } + catch(e){/*not supported*/} if(!ip || !type || !localIP || active != "true") continue; var addressSaved = false; @@ -364,17 +434,32 @@ StatsCollector.prototype.processStatsReport = function () { continue; } - if (now.type != 'ssrc') { + if(now.type == "candidatepair") + { + if(now.state == "succeeded") + continue; + + var local = this.currentStatsReport[now.localCandidateId]; + var remote = this.currentStatsReport[now.remoteCandidateId]; + PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber, + ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport}); + + } + + if (now.type != 'ssrc' && now.type != "outboundrtp" && + now.type != "inboundrtp") { continue; } var before = this.baselineStatsReport[idx]; if (!before) { - console.warn(now.stat('ssrc') + ' not enough data'); + console.warn(getStatValue(now, 'ssrc') + ' not enough data'); continue; } - var ssrc = now.stat('ssrc'); + var ssrc = getStatValue(now, 'ssrc'); + if(!ssrc) + continue; var jid = ssrc2jid[ssrc]; if (!jid) { console.warn("No jid for ssrc: " + ssrc); @@ -390,31 +475,30 @@ StatsCollector.prototype.processStatsReport = function () { var isDownloadStream = true; var key = 'packetsReceived'; - if (!now.stat(key)) + if (!getStatValue(now, key)) { isDownloadStream = false; key = 'packetsSent'; - if (!now.stat(key)) + if (!getStatValue(now, key)) { - console.error("No packetsReceived nor packetSent stat found"); - this.stop(); - return; + console.warn("No packetsReceived nor packetSent stat found"); + continue; } } - var packetsNow = now.stat(key); + var packetsNow = getStatValue(now, key); if(!packetsNow || packetsNow < 0) packetsNow = 0; - var packetsBefore = before.stat(key); + var packetsBefore = getStatValue(before, key); if(!packetsBefore || packetsBefore < 0) packetsBefore = 0; var packetRate = packetsNow - packetsBefore; if(!packetRate || packetRate < 0) packetRate = 0; - var currentLoss = now.stat('packetsLost'); + var currentLoss = getStatValue(now, 'packetsLost'); if(!currentLoss || currentLoss < 0) currentLoss = 0; - var previousLoss = before.stat('packetsLost'); + var previousLoss = getStatValue(before, 'packetsLost'); if(!previousLoss || previousLoss < 0) previousLoss = 0; var lossRate = currentLoss - previousLoss; @@ -427,16 +511,18 @@ StatsCollector.prototype.processStatsReport = function () { "packetsLost": lossRate, "isDownloadStream": isDownloadStream}); + var bytesReceived = 0, bytesSent = 0; - if(now.stat("bytesReceived")) + if(getStatValue(now, "bytesReceived")) { - bytesReceived = now.stat("bytesReceived") - - before.stat("bytesReceived"); + bytesReceived = getStatValue(now, "bytesReceived") - + getStatValue(before, "bytesReceived"); } - if(now.stat("bytesSent")) + if(getStatValue(now, "bytesSent")) { - bytesSent = now.stat("bytesSent") - before.stat("bytesSent"); + bytesSent = getStatValue(now, "bytesSent") - + getStatValue(before, "bytesSent"); } var time = Math.round((now.timestamp - before.timestamp) / 1000); @@ -461,19 +547,21 @@ StatsCollector.prototype.processStatsReport = function () { jidStats.setSsrcBitrate(ssrc, { "download": bytesReceived, "upload": bytesSent}); + var resolution = {height: null, width: null}; - if(now.stat("googFrameHeightReceived") && - now.stat("googFrameWidthReceived")) - { - resolution.height = now.stat("googFrameHeightReceived"); - resolution.width = now.stat("googFrameWidthReceived"); - } - else if(now.stat("googFrameHeightSent") && - now.stat("googFrameWidthSent")) - { - resolution.height = now.stat("googFrameHeightSent"); - resolution.width = now.stat("googFrameWidthSent"); + try { + if (getStatValue(now, "googFrameHeightReceived") && + getStatValue(now, "googFrameWidthReceived")) { + resolution.height = getStatValue(now, "googFrameHeightReceived"); + resolution.width = getStatValue(now, "googFrameWidthReceived"); + } + else if (getStatValue(now, "googFrameHeightSent") && + getStatValue(now, "googFrameWidthSent")) { + resolution.height = getStatValue(now, "googFrameHeightSent"); + resolution.width = getStatValue(now, "googFrameWidthSent"); + } } + catch(e){/*not supported*/} if(resolution.height && resolution.width) { @@ -515,6 +603,8 @@ StatsCollector.prototype.processStatsReport = function () { self.jid2stats[jid].ssrc2bitrate[ssrc].download; bitrateUpload += self.jid2stats[jid].ssrc2bitrate[ssrc].upload; + + delete self.jid2stats[jid].ssrc2bitrate[ssrc]; } ); resolutions[jid] = self.jid2stats[jid].ssrc2resolution; @@ -566,11 +656,11 @@ StatsCollector.prototype.processAudioLevelReport = function () var before = this.baselineAudioLevelsReport[idx]; if (!before) { - console.warn(now.stat('ssrc') + ' not enough data'); + console.warn(getStatValue(now, 'ssrc') + ' not enough data'); continue; } - var ssrc = now.stat('ssrc'); + var ssrc = getStatValue(now, 'ssrc'); var jid = ssrc2jid[ssrc]; if (!jid) { @@ -586,9 +676,19 @@ StatsCollector.prototype.processAudioLevelReport = function () } // Audio level - var audioLevel = now.stat('audioInputLevel'); - if (!audioLevel) - audioLevel = now.stat('audioOutputLevel'); + var audioLevel = null; + + try { + audioLevel = getStatValue(now, 'audioInputLevel'); + if (!audioLevel) + audioLevel = getStatValue(now, 'audioOutputLevel'); + } + catch(e) {/*not supported*/ + console.warn("Audio Levels are not available in the statistics."); + clearInterval(this.audioLevelsIntervalId); + return; + } + if (audioLevel) { // TODO: can't find specs about what this value really is, @@ -603,3 +703,10 @@ StatsCollector.prototype.processAudioLevelReport = function () }; + +function getStatValue(item, name) { + if(!keyMap[RTC.browser][name]) + throw "The property isn't supported!"; + var key = keyMap[RTC.browser][name]; + return RTC.browser == "chrome"? item.stat(key) : item[key]; +} \ No newline at end of file diff --git a/videolayout.js b/videolayout.js index 0076f2ea8c..941c041b2a 100644 --- a/videolayout.js +++ b/videolayout.js @@ -9,6 +9,9 @@ var VideoLayout = (function (my) { updateInProgress: false, newSrc: '' }; + + var defaultLocalDisplayName = "Me"; + my.connectionIndicators = {}; my.isInLastN = function(resource) { @@ -17,9 +20,13 @@ var VideoLayout = (function (my) { || (lastNEndpointsCache && lastNEndpointsCache.indexOf(resource) !== -1); }; - my.changeLocalAudio = function(stream) { + my.changeLocalStream = function (stream) { connection.jingle.localAudio = stream; + VideoLayout.changeLocalVideo(stream, true); + } + my.changeLocalAudio = function(stream) { + connection.jingle.localAudio = stream; RTC.attachMediaStream($('#localAudio'), stream); document.getElementById('localAudio').autoplay = true; document.getElementById('localAudio').volume = 0; @@ -33,7 +40,7 @@ var VideoLayout = (function (my) { connection.jingle.localVideo = stream; var localVideo = document.createElement('video'); - localVideo.id = 'localVideo_' + stream.id; + localVideo.id = 'localVideo_' + RTC.getStreamID(stream); localVideo.autoplay = true; localVideo.volume = 0; // is it required if audio is separated ? localVideo.oncontextmenu = function () { return false; }; @@ -55,10 +62,10 @@ var VideoLayout = (function (my) { // Add click handler to both video and video wrapper elements in case // there's no video. localVideoSelector.click(function () { - VideoLayout.handleVideoThumbClicked(localVideo.src); + VideoLayout.handleVideoThumbClicked(RTC.getVideoSrc(localVideo), false, connection.emuc.myroomjid); }); $('#localVideoContainer').click(function () { - VideoLayout.handleVideoThumbClicked(localVideo.src); + VideoLayout.handleVideoThumbClicked(RTC.getVideoSrc(localVideo), false, connection.emuc.myroomjid); }); // Add hover handler @@ -68,14 +75,14 @@ var VideoLayout = (function (my) { }, function() { if (!VideoLayout.isLargeVideoVisible() - || localVideo.src !== $('#largeVideo').attr('src')) + || RTC.getVideoSrc(localVideo) !== RTC.getVideoSrc($('#largeVideo')[0])) VideoLayout.showDisplayName('localVideoContainer', false); } ); // Add stream ended handler stream.onended = function () { localVideoContainer.removeChild(localVideo); - VideoLayout.updateRemovedVideo(localVideo.src); + VideoLayout.updateRemovedVideo(RTC.getVideoSrc(localVideo)); }; // Flip video x axis if needed flipXLocalVideo = flipX; @@ -86,9 +93,16 @@ var VideoLayout = (function (my) { var videoStream = simulcast.getLocalVideoStream(); RTC.attachMediaStream(localVideoSelector, videoStream); - localVideoSrc = localVideo.src; + localVideoSrc = RTC.getVideoSrc(localVideo); + + var myResourceJid = null; + if(connection.emuc.myroomjid) + { + myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid); + } + VideoLayout.updateLargeVideo(localVideoSrc, 0, + myResourceJid); - VideoLayout.updateLargeVideo(localVideoSrc, 0); }; /** @@ -97,7 +111,7 @@ var VideoLayout = (function (my) { * @param removedVideoSrc src stream identifier of the video. */ my.updateRemovedVideo = function(removedVideoSrc) { - if (removedVideoSrc === $('#largeVideo').attr('src')) { + if (removedVideoSrc === RTC.getVideoSrc($('#largeVideo')[0])) { // this is currently displayed as large // pick the last visible video in the row // if nobody else is left, this picks the local video @@ -109,7 +123,7 @@ var VideoLayout = (function (my) { console.info("Last visible video no longer exists"); pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0); - if (!pick || !pick.src) { + if (!pick || !RTC.getVideoSrc(pick)) { // Try local video console.info("Fallback to local video..."); pick = $('#remoteVideos>span>span>video').get(0); @@ -118,20 +132,38 @@ var VideoLayout = (function (my) { // mute if localvideo if (pick) { - VideoLayout.updateLargeVideo(pick.src, pick.volume); + var container = pick.parentNode; + var jid = null; + if(container) + { + if(container.id == "localVideoWrapper") + { + jid = Strophe.getResourceFromJid(connection.emuc.myroomjid); + } + else + { + jid = VideoLayout.getPeerContainerResourceJid(container); + } + } + + VideoLayout.updateLargeVideo(RTC.getVideoSrc(pick), pick.volume, jid); } else { console.warn("Failed to elect large video"); } } }; + my.getLargeVideoState = function () { + return largeVideoState; + } + /** * Updates the large video with the given new video source. */ - my.updateLargeVideo = function(newSrc, vol) { + my.updateLargeVideo = function(newSrc, vol, jid) { console.log('hover in', newSrc); - if ($('#largeVideo').attr('src') != newSrc) { + if (RTC.getVideoSrc($('#largeVideo')[0]) != newSrc) { $('#activeSpeakerAvatar').css('visibility', 'hidden'); // Due to the simulcast the localVideoSrc may have changed when the @@ -144,15 +176,22 @@ var VideoLayout = (function (my) { largeVideoState.newSrc = newSrc; largeVideoState.isVisible = $('#largeVideo').is(':visible'); - largeVideoState.isDesktop = isVideoSrcDesktop(newSrc); - largeVideoState.userJid = getJidFromVideoSrc(newSrc); + largeVideoState.isDesktop = isVideoSrcDesktop(jid); + if(jid2Ssrc[largeVideoState.userJid] || + (connection && connection.emuc.myroomjid && + largeVideoState.userJid == Strophe.getResourceFromJid(connection.emuc.myroomjid))) + { + largeVideoState.oldJid = largeVideoState.userJid; + } + else + { + largeVideoState.oldJid = null; + } + largeVideoState.userJid = jid; // Screen stream is already rotated largeVideoState.flipX = (newSrc === localVideoSrc) && flipXLocalVideo; - var oldSrc = $('#largeVideo').attr('src'); - largeVideoState.oldJid = getJidFromVideoSrc(oldSrc); - var userChanged = false; if (largeVideoState.oldJid != largeVideoState.userJid) { userChanged = true; @@ -170,7 +209,8 @@ var VideoLayout = (function (my) { if (!userChanged && largeVideoState.preload && largeVideoState.preload != null - && $(largeVideoState.preload).attr('src') == newSrc) { + && RTC.getVideoSrc($(largeVideoState.preload)[0]) == newSrc) + { console.info('Switching to preloaded video'); var attributes = $('#largeVideo').prop("attributes"); @@ -196,7 +236,7 @@ var VideoLayout = (function (my) { largeVideoState.preload = null; largeVideoState.preload_ssrc = 0; } else { - $('#largeVideo').attr('src', largeVideoState.newSrc); + RTC.setVideoSrc($('#largeVideo')[0], largeVideoState.newSrc); } var videoTransform = document.getElementById('largeVideo') @@ -224,14 +264,12 @@ var VideoLayout = (function (my) { // Only if the large video is currently visible. // Disable previous dominant speaker video. if (largeVideoState.oldJid) { - var oldResourceJid = Strophe.getResourceFromJid(largeVideoState.oldJid); - VideoLayout.enableDominantSpeaker(oldResourceJid, false); + VideoLayout.enableDominantSpeaker(largeVideoState.oldJid, false); } // Enable new dominant speaker in the remote videos section. if (largeVideoState.userJid) { - var resourceJid = Strophe.getResourceFromJid(largeVideoState.userJid); - VideoLayout.enableDominantSpeaker(resourceJid, true); + VideoLayout.enableDominantSpeaker(largeVideoState.userJid, true); } if (userChanged && largeVideoState.isVisible) { @@ -256,17 +294,20 @@ var VideoLayout = (function (my) { } }; - my.handleVideoThumbClicked = function(videoSrc, noPinnedEndpointChangedEvent) { + my.handleVideoThumbClicked = function(videoSrc, noPinnedEndpointChangedEvent, jid) { // Restore style for previously focused video - var focusJid = getJidFromVideoSrc(focusedVideoSrc); - var oldContainer = getParticipantContainer(focusJid); + var oldContainer = null; + if(focusedVideoSrc) { + var focusJid = focusedVideoSrc.jid; + oldContainer = getParticipantContainer(focusJid); + } if (oldContainer) { oldContainer.removeClass("videoContainerFocused"); } // Unlock current focused. - if (focusedVideoSrc === videoSrc) + if (focusedVideoSrc && focusedVideoSrc.src === videoSrc) { focusedVideoSrc = null; var dominantSpeakerVideo = null; @@ -277,7 +318,7 @@ var VideoLayout = (function (my) { .get(0); if (dominantSpeakerVideo) { - VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1); + VideoLayout.updateLargeVideo(RTC.getVideoSrc(dominantSpeakerVideo), 1, currentDominantSpeaker); } } @@ -288,17 +329,19 @@ var VideoLayout = (function (my) { } // Lock new video - focusedVideoSrc = videoSrc; + focusedVideoSrc = { + src: videoSrc, + jid: jid + }; // Update focused/pinned interface. - var userJid = getJidFromVideoSrc(videoSrc); - if (userJid) + if (jid) { - var container = getParticipantContainer(userJid); + var container = getParticipantContainer(jid); container.addClass("videoContainerFocused"); if (!noPinnedEndpointChangedEvent) { - $(document).trigger("pinnedendpointchanged", [userJid]); + $(document).trigger("pinnedendpointchanged", [jid]); } } @@ -310,7 +353,7 @@ var VideoLayout = (function (my) { // this isn't a prezi. $(document).trigger("video.selected", [false]); - VideoLayout.updateLargeVideo(videoSrc, 1); + VideoLayout.updateLargeVideo(videoSrc, 1, Strophe.getResourceFromJid(jid)); $('audio').each(function (idx, el) { if (el.id.indexOf('mixedmslabel') !== -1) { @@ -356,8 +399,7 @@ var VideoLayout = (function (my) { * Shows/hides the large video. */ my.setLargeVideoVisible = function(isVisible) { - var largeVideoJid = getJidFromVideoSrc($('#largeVideo').attr('src')); - var resourceJid = Strophe.getResourceFromJid(largeVideoJid); + var resourceJid = largeVideoState.userJid; if (isVisible) { $('#largeVideo').css({visibility: 'visible'}); @@ -457,7 +499,7 @@ var VideoLayout = (function (my) { ? document.createElement('video') : document.createElement('audio'); var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') - + sid + '_' + stream.id; + + sid + '_' + RTC.getStreamID(stream); element.id = id; element.autoplay = true; @@ -487,10 +529,8 @@ var VideoLayout = (function (my) { var videoStream = simulcast.getReceivingVideoStream(stream); RTC.attachMediaStream(sel, videoStream); - if (isVideo) { - waitForRemoteVideo(sel, thessrc, stream); - } - + if (isVideo) + waitForRemoteVideo(sel, thessrc, stream, peerJid); } stream.onended = function () { @@ -512,7 +552,7 @@ var VideoLayout = (function (my) { var videoThumb = $('#' + container.id + '>video').get(0); if (videoThumb) - VideoLayout.handleVideoThumbClicked(videoThumb.src); + VideoLayout.handleVideoThumbClicked(RTC.getVideoSrc(videoThumb), false, peerJid); event.preventDefault(); return false; @@ -527,13 +567,13 @@ var VideoLayout = (function (my) { var videoSrc = null; if ($('#' + container.id + '>video') && $('#' + container.id + '>video').length > 0) { - videoSrc = $('#' + container.id + '>video').get(0).src; + videoSrc = RTC.getVideoSrc($('#' + container.id + '>video').get(0)); } // If the video has been "pinned" by the user we want to // keep the display name on place. if (!VideoLayout.isLargeVideoVisible() - || videoSrc !== $('#largeVideo').attr('src')) + || videoSrc !== RTC.getVideoSrc($('#largeVideo')[0])) VideoLayout.showDisplayName(container.id, false); } ); @@ -558,13 +598,11 @@ var VideoLayout = (function (my) { var removedVideoSrc = null; if (isVideo) { select = $('#' + container.id + '>video'); - removedVideoSrc = select.get(0).src; + removedVideoSrc = RTC.getVideoSrc(select.get(0)); } else select = $('#' + container.id + '>audio'); - // Remove video source from the mapping. - delete videoSrcToSsrc[removedVideoSrc]; // Mark video as removed to cancel waiting loop(if video is removed // before has started) @@ -1004,7 +1042,6 @@ var VideoLayout = (function (my) { videoSpan = document.getElementById(videoContainerId); if (!videoSpan) { - console.error("No video element for jid", resourceJid); return; } @@ -1220,22 +1257,6 @@ var VideoLayout = (function (my) { return containerElement.id.substring(i + 12); }; - my.getLargeVideoResource = function () { - var largeVideoJid, largeVideoResource; - - // Another approach could be to compare the srcs of the thumbnails and - // then call getPeerContainerResourceJid. - - var largeVideoSsrc - = videoSrcToSsrc[$('#largeVideo').attr('src')]; - - if (largeVideoSsrc - /* variables/state checking to prevent exceptions */ - && (largeVideoJid = ssrc2jid[largeVideoSsrc]) - && (largeVideoResource = Strophe.getResourceFromJid(largeVideoJid))) - return largeVideoResource; - }; - /** * Adds the remote video menu element for the given jid in the * given parentElement. @@ -1338,7 +1359,7 @@ var VideoLayout = (function (my) { // We have a video src, great! Let's update the large video // now. - VideoLayout.handleVideoThumbClicked(videoThumb.src); + VideoLayout.handleVideoThumbClicked(videoThumb.src, false, jid); } else { // If we don't have a video src for jid, there's absolutely @@ -1474,7 +1495,7 @@ var VideoLayout = (function (my) { // Update the large video if the video source is already available, // otherwise wait for the "videoactive.jingle" event. if (video.length && video[0].currentTime > 0) - VideoLayout.updateLargeVideo(video[0].src); + VideoLayout.updateLargeVideo(RTC.getVideoSrc(video[0]), resourceJid); } }); @@ -1553,7 +1574,7 @@ var VideoLayout = (function (my) { // it is no longer being received. If resourceJid was being // displayed in the large video we have to switch to another // user. - var largeVideoResource = VideoLayout.getLargeVideoResource(); + var largeVideoResource = largeVideoState.userJid; if (!updateLargeVideo && resourceJid === largeVideoResource) { updateLargeVideo = true; } @@ -1578,18 +1599,17 @@ var VideoLayout = (function (my) { var videoStream = simulcast.getReceivingVideoStream( mediaStream.stream); RTC.attachMediaStream(sel, videoStream); - videoSrcToSsrc[sel.attr('src')] = mediaStream.ssrc; if (lastNPickupJid == mediaStream.peerjid) { // Clean up the lastN pickup jid. lastNPickupJid = null; // Don't fire the events again, they've already // been fired in the contact list click handler. - VideoLayout.handleVideoThumbClicked($(sel).attr('src'), false); + VideoLayout.handleVideoThumbClicked($(sel).attr('src'), false, mediaStream.peerjid); updateLargeVideo = false; } - waitForRemoteVideo(sel, mediaStream.ssrc, mediaStream.stream); + waitForRemoteVideo(sel, mediaStream.ssrc, mediaStream.stream, resourceJid); } }) } @@ -1646,7 +1666,7 @@ var VideoLayout = (function (my) { || (parentResourceJid && VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) { - VideoLayout.updateLargeVideo(videoelem.attr('src'), 1); + VideoLayout.updateLargeVideo(RTC.getVideoSrc(videoelem[0]), 1, parentResourceJid); } VideoLayout.showModeratorIndicator(); @@ -1657,7 +1677,15 @@ var VideoLayout = (function (my) { endpointSimulcastLayers.forEach(function (esl) { var resource = esl.endpoint; - if (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1) { + + // if lastN is enabled *and* the endpoint is *not* in the lastN set, + // then ignore the event (= do not preload anything). + // + // The bridge could probably stop sending this message if it's for + // an endpoint that's not in lastN. + + if (lastNCount != -1 + && (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1)) { return; } @@ -1674,12 +1702,8 @@ var VideoLayout = (function (my) { console.info([esl, primarySSRC, msid, session, electedStream]); var msidParts = msid.split(' '); - var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join('')); - // FIXME(gp) here we should use the VideoLayout.getPeerContainerResource - // and VideoLayout.getLargeVideoResource methods. - var preload = (ssrc2jid[videoSrcToSsrc[selRemoteVideo.attr('src')]] - == ssrc2jid[videoSrcToSsrc[largeVideoState.newSrc]]); + var preload = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC]) == largeVideoState.userJid); if (preload) { if (largeVideoState.preload) @@ -1691,9 +1715,7 @@ var VideoLayout = (function (my) { // ssrcs are unique in an rtp session largeVideoState.preload_ssrc = primarySSRC; - var electedStreamUrl = webkitURL.createObjectURL(electedStream); - largeVideoState.preload - .attr('src', electedStreamUrl); + RTC.attachMediaStream(largeVideoState.preload, electedStream) } } else { @@ -1709,7 +1731,19 @@ var VideoLayout = (function (my) { endpointSimulcastLayers.forEach(function (esl) { var resource = esl.endpoint; - if (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1) { + + // if lastN is enabled *and* the endpoint is *not* in the lastN set, + // then ignore the event (= do not change large video/thumbnail + // SRCs). + // + // Note that even if we ignore the "changed" event in this event + // handler, the bridge must continue sending these events because + // the simulcast code in simulcast.js uses it to know what's going + // to be streamed by the bridge when/if the endpoint gets back into + // the lastN set. + + if (lastNCount != -1 + && (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1)) { return; } @@ -1729,21 +1763,15 @@ var VideoLayout = (function (my) { var msidParts = msid.split(' '); var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join('')); - // FIXME(gp) here we should use the VideoLayout.getPeerContainerResource - // and VideoLayout.getLargeVideoResource methods. - var updateLargeVideo = (ssrc2jid[videoSrcToSsrc[selRemoteVideo.attr('src')]] - == ssrc2jid[videoSrcToSsrc[largeVideoState.newSrc]]); - - // We should only update the focused video src if it's not a - // falsy value. - var updateFocusedVideoSrc - = focusedVideoSrc && focusedVideoSrc !== '' - && (selRemoteVideo.attr('src') == focusedVideoSrc); + var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC]) + == largeVideoState.userJid); + var updateFocusedVideoSrc = (focusedVideoSrc && focusedVideoSrc.src && focusedVideoSrc.src != '' && + (RTC.getVideoSrc(selRemoteVideo[0]) == focusedVideoSrc.src)); var electedStreamUrl; if (largeVideoState.preload_ssrc == primarySSRC) { - electedStreamUrl = $(largeVideoState.preload).attr('src'); + RTC.setVideoSrc(selRemoteVideo[0], RTC.getVideoSrc(largeVideoState.preload[0])); } else { @@ -1754,18 +1782,19 @@ var VideoLayout = (function (my) { largeVideoState.preload_ssrc = 0; - electedStreamUrl = webkitURL.createObjectURL(electedStream); + RTC.attachMediaStream(selRemoteVideo, electedStream); } - selRemoteVideo.attr('src', electedStreamUrl); - videoSrcToSsrc[selRemoteVideo.attr('src')] = primarySSRC + ''; // what we store there is typeof string. + var jid = ssrc2jid[primarySSRC]; + jid2Ssrc[jid] = primarySSRC; if (updateLargeVideo) { - VideoLayout.updateLargeVideo(electedStreamUrl); + VideoLayout.updateLargeVideo(RTC.getVideoSrc(selRemoteVideo[0]), null, + Strophe.getResourceFromJid(jid)); } if (updateFocusedVideoSrc) { - focusedVideoSrc = electedStreamUrl; + focusedVideoSrc.src = RTC.getVideoSrc(selRemoteVideo[0]); } var videoId; @@ -1889,19 +1918,25 @@ var VideoLayout = (function (my) { if(this.jid==null) { resolution = ""; - for(var i in this.resolution) + if(this.resolution == null || !Object.keys(this.resolution) + || Object.keys(this.resolution).length == 0) { - resolutionValue = this.resolution[i]; - if(resolutionValue) + resolution = "N/A"; + } + else + for(var i in this.resolution) { - if(resolutionValue.height && - resolutionValue.width) + resolutionValue = this.resolution[i]; + if(resolutionValue) { - resolution += (resolution == ""? "" : ", ") - + resolutionValue.width + "x" + resolutionValue.height; + if(resolutionValue.height && + resolutionValue.width) + { + resolution += (resolution == ""? "" : ", ") + + resolutionValue.width + "x" + resolutionValue.height; + } } } - } } else if(!resolutionValue || !resolutionValue.height ||