Browse Source

Merge pulll request #138 from longsleep/renegotiation

* renegotiation:
  Disable enableRenegotiationSupport per default to make the stuff mergeable.
  Add pc registry to usermedia to trigger media updates to all pcs where the usermedia stream is used.
  Implemented negotiation workaround for Firefox.
  Implemented offer/answer creation on renegotiationneeded event.
  Implement workaround for FF not supporting onnegotiationneeded.
  Changed offer generation to generate offer when negotiation is required and signaling state is stable.
pull/112/head
Simon Eisenmann 11 years ago
parent
commit
2228d6bcee
  1. 115
      static/js/directives/audiovideo.js
  2. 20
      static/js/mediastream/peercall.js
  3. 7
      static/js/mediastream/peerconference.js
  4. 33
      static/js/mediastream/peerconnection.js
  5. 217
      static/js/mediastream/usermedia.js
  6. 104
      static/js/mediastream/webrtc.js
  7. 28
      static/js/services/videolayout.js
  8. 2
      static/partials/audiovideopeer.html

115
static/js/directives/audiovideo.js

@ -26,8 +26,12 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -26,8 +26,12 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
var peers = {};
var events = $({});
var streams = {};
var getStreamId = function(stream, currentcall) {
var id = currentcall.id + "-" + stream.id;
console.log("Created stream ID", id);
return id;
};
$scope.container = $element.get(0);
$scope.layoutparent = $element.parent();
@ -39,6 +43,9 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -39,6 +43,9 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
$scope.hasUsermedia = false;
$scope.isActive = false;
$scope.haveStreams = false;
$scope.peersTalking = {};
$scope.rendererName = $scope.defaultRendererName = "democrazy";
@ -46,31 +53,32 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -46,31 +53,32 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
$scope.addRemoteStream = function(stream, currentcall) {
//console.log("Add remote stream to scope", pc.id, stream);
var id = getStreamId(stream, currentcall);
if (streams.hasOwnProperty(id)) {
console.warn("Cowardly refusing to add stream id twice", id, currentcall);
return;
}
//console.log("Add remote stream to scope", stream.id, stream, currentcall);
// Create scope.
var subscope = $scope.$new(true);
var subscope = $scope.$new();
var peerid = subscope.peerid = currentcall.id;
buddyData.push(peerid);
subscope.withvideo = false;
subscope.onlyaudio = false;
subscope.talking = false;
subscope.destroyed = false;
subscope.applyTalking = function(talking) {
subscope.talking = !! talking;
safeApply(subscope);
};
subscope.$on("active", function() {
console.log("Stream scope is now active", peerid);
events.triggerHandler("active." + peerid, [subscope, currentcall, stream]);
console.log("Stream scope is now active", id, peerid);
});
subscope.$on("$destroy", function() {
console.log("Destroyed scope for audiovideo", subscope);
console.log("Destroyed scope for stream", id, peerid);
subscope.destroyed = true;
});
console.log("Created stream scope", peerid);
console.log("Created stream scope", id, peerid);
// Add created scope.
peers[peerid] = subscope;
streams[id] = subscope;
// Render template.
peerTemplate(subscope, function(clonedElement, scope) {
@ -118,10 +126,13 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -118,10 +126,13 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
$scope.removeRemoteStream = function(stream, currentcall) {
var subscope = peers[currentcall.id];
//console.log("remove stream", stream, stream.id, currentcall);
var id = getStreamId(stream, currentcall);
var subscope = streams[id];
if (subscope) {
buddyData.pop(currentcall.id);
delete peers[currentcall.id];
delete streams[id];
//console.log("remove scope", subscope);
if (subscope.element) {
subscope.element.remove();
@ -134,17 +145,9 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -134,17 +145,9 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
// Talking updates receiver.
mediaStream.api.e.on("received.talking", function(event, id, from, talking) {
var scope = peers[from];
//console.log("received.talking", talking, scope);
if (scope) {
scope.applyTalking(talking);
} else {
console.log("Received talking state without scope -> adding event.", from, talking);
events.one("active." + from, function(event, scope) {
console.log("Applying previously received talking state", from, talking);
scope.applyTalking(talking);
});
}
$scope.$apply(function(scope) {
scope.peersTalking[from] = !!talking;
});
});
$scope.$on("active", function(currentcall) {
@ -177,27 +180,36 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -177,27 +180,36 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
mediaStream.webrtc.e.on("usermedia", function(event, usermedia) {
//console.log("XXXXXXXXXXXXXXXXXXXXXXXXX usermedia event", usermedia);
$scope.hasUsermedia = true;
usermedia.attachMediaStream($scope.localVideo);
var count = 0;
var waitForLocalVideo = function() {
if (!$scope.hasUsermedia) {
return;
}
if ($scope.localVideo.videoWidth > 0) {
$scope.localVideo.style.opacity = 1;
$scope.redraw();
} else {
count++;
if (count < 100) {
setTimeout(waitForLocalVideo, 100);
//console.log("XXXX XXXXXXXXXXXXXXXXXXXXX usermedia event", usermedia);
if ($scope.haveStreams) {
usermedia.attachMediaStream($scope.miniVideo);
$scope.redraw();
} else {
$scope.hasUsermedia = true;
usermedia.attachMediaStream($scope.localVideo);
var count = 0;
var waitForLocalVideo = function() {
if (!$scope.hasUsermedia) {
return;
}
if ($scope.localVideo.videoWidth > 0) {
$scope.localVideo.style.opacity = 1;
$scope.redraw();
} else {
console.warn("Timeout while waiting for local video.")
count++;
if (count < 100) {
setTimeout(waitForLocalVideo, 100);
} else {
console.warn("Timeout while waiting for local video.")
}
}
}
};
waitForLocalVideo();
};
waitForLocalVideo();
}
});
@ -205,6 +217,7 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -205,6 +217,7 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
$scope.hasUsermedia = false;
$scope.isActive = false;
$scope.peersTalking = {};
if (BigScreen.enabled) {
BigScreen.exit();
}
@ -220,20 +233,22 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -220,20 +233,22 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
$scope.localVideo.style.opacity = 0;
$scope.remoteVideos.style.opacity = 0;
$element.removeClass('active');
_.each(peers, function(scope, k) {
_.each(streams, function(scope, k) {
scope.$destroy();
delete peers[k];
delete streams[k];
});
$scope.rendererName = $scope.defaultRendererName;
$scope.haveStreams = false;
});
mediaStream.webrtc.e.on("streamadded", function(event, stream, currentcall) {
console.log("Remote stream added.", stream, currentcall);
if (_.isEmpty(peers)) {
if (!$scope.haveStreams) {
//console.log("First stream");
$window.reattachMediaStream($scope.miniVideo, $scope.localVideo);
$scope.haveStreams = true;
}
$scope.addRemoteStream(stream, currentcall);
@ -247,7 +262,7 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -247,7 +262,7 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
});
return {
peers: peers
streams: streams
};
}];

20
static/js/mediastream/peercall.js

@ -138,7 +138,7 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection @@ -138,7 +138,7 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
// after the remote SDP was set successfully.
_.defer(_.bind(function() {
_.each(peerconnection.getRemoteStreams(), _.bind(function(stream) {
if (!this.streams.hasOwnProperty(stream) && (stream.getAudioTracks().length > 0 || stream.getVideoTracks().length > 0)) {
if (!this.streams.hasOwnProperty(stream.id) && (stream.getAudioTracks().length > 0 || stream.getVideoTracks().length > 0)) {
// NOTE(longsleep): Add stream here when it has at least one audio or video track, to avoid FF >= 33 to add it multiple times.
console.log("Adding stream after remote SDP success.", stream);
this.onRemoteStreamAdded(stream);
@ -182,7 +182,11 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection @@ -182,7 +182,11 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
PeerCall.prototype.onRemoteStreamAdded = function(stream) {
this.streams[stream] = true;
var id = stream.id;
if (this.streams.hasOwnProperty(id)) {
return;
}
this.streams[id] = stream;
this.e.triggerHandler("remoteStreamAdded", [stream, this]);
};
@ -191,16 +195,17 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection @@ -191,16 +195,17 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
this.e.triggerHandler("remoteStreamRemoved", [stream, this]);
if (stream) {
delete this.streams[stream];
delete this.streams[stream.id];
}
};
PeerCall.prototype.onNegotiationNeeded = function(peerconnection) {
PeerCall.prototype.onNegotiationNeeded = function() {
if (!this.negotiationNeeded) {
this.negotiationNeeded = true;
console.log("Negotiation needed.", this);
this.e.triggerHandler("negotiationNeeded", [this]);
}
};
@ -298,13 +303,18 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection @@ -298,13 +303,18 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
datachannel.close();
});
this.datachannels = {};
this.streams = {};
if (this.peerconnection) {
this.peerconnection.close();
this.peerconnection = null;
}
// Trigger event for all previously added streams.
_.each(this.streams, _.bind(function(stream, id) {
this.e.triggerHandler("remoteStreamRemoved", [stream, this]);
}, this));
this.streams = {};
console.log("Peercall close", this);
this.e.triggerHandler("closed", [this]);

7
static/js/mediastream/peerconference.js

@ -92,13 +92,16 @@ define(['underscore', 'mediastream/peercall'], function(_, PeerCall) { @@ -92,13 +92,16 @@ define(['underscore', 'mediastream/peercall'], function(_, PeerCall) {
console.log("Creating PeerConnection", call);
call.createPeerConnection(_.bind(function(peerconnection) {
// Success call.
call.e.on("negotiationNeeded", _.bind(function(event, extracall) {
this.webrtc.sendOfferWhenNegotiationNeeded(extracall);
}, this));
if (this.webrtc.usermedia) {
this.webrtc.usermedia.addToPeerConnection(peerconnection);
}
call.createOffer(_.bind(function(sessionDescription, extracall) {
/*call.createOffer(_.bind(function(sessionDescription, extracall) {
console.log("Sending offer with sessionDescription", sessionDescription, extracall.id);
this.webrtc.api.sendOffer(extracall.id, sessionDescription);
}, this));
}, this));*/
}, this), _.bind(function() {
// Error call.
console.error("Failed to create peer connection for conference call.");

33
static/js/mediastream/peerconnection.js

@ -36,7 +36,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -36,7 +36,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
this.createPeerConnection(currentcall);
}
}
};
PeerConnection.prototype.createPeerConnection = function(currentcall) {
@ -70,11 +70,22 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -70,11 +70,22 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
// for example https://bugzilla.mozilla.org/show_bug.cgi?id=998546.
pc.onaddstream = _.bind(this.onRemoteStreamAdded, this);
pc.onremovestream = _.bind(this.onRemoteStreamRemoved, this);
pc.onnegotiationneeded = _.bind(this.onNegotiationNeeded, this);
if (webrtcDetectedBrowser === "firefox") {
// NOTE(longsleep): onnegotiationneeded is not supported by Firefox. We trigger it
// manually when a stream is added or removed.
// https://bugzilla.mozilla.org/show_bug.cgi?id=840728
this.negotiationNeeded = _.bind(function() {
if (this.currentcall.initiate) {
// Trigger onNegotiationNeeded once for Firefox.
console.log("Negotiation needed.");
this.onNegotiationNeeded({target: this.pc});
}
}, this);
} else {
pc.onnegotiationneeded = _.bind(this.onNegotiationNeeded, this);
}
pc.ondatachannel = _.bind(this.onDatachannel, this);
pc.onsignalingstatechange = function(event) {
// XXX(longsleep): Remove this or handle it in a real function.
// XXX(longsleep): Firefox 25 does send event as a string (like stable).
console.debug("Signaling state changed", pc.signalingState);
};
// NOTE(longsleep):
@ -111,6 +122,10 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -111,6 +122,10 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
};
PeerConnection.prototype.negotiationNeeded = function() {
// Per default this does nothing as the browser is expected to handle this.
};
PeerConnection.prototype.createDatachannel = function(label, init) {
if (!label) {
@ -224,13 +239,9 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -224,13 +239,9 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
PeerConnection.prototype.onNegotiationNeeded = function(event) {
// XXX(longsleep): Renegotiation seems to break video streams on Chrome 31.
// XXX(longsleep): Renegotiation can happen from both sides, meaning this
// could switch offer/answer side - oh crap.
var peerconnection = event.target;
if (peerconnection === this.pc) {
//console.log("Negotiation needed.", peerconnection.remoteDescription, peerconnection.iceConnectionState, peerconnection.signalingState, this);
this.currentcall.onNegotiationNeeded(this);
this.currentcall.onNegotiationNeeded();
}
};
@ -244,8 +255,6 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -244,8 +255,6 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
this.pc.close();
}
this.currentcall.onRemoteStreamRemoved(null);
this.datachannel = null;
this.pc = null;
@ -271,12 +280,14 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -271,12 +280,14 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
PeerConnection.prototype.addStream = function() {
_.defer(this.negotiationNeeded);
return this.pc.addStream.apply(this.pc, arguments);
};
PeerConnection.prototype.removeStream = function() {
_.defer(this.negotiationNeeded);
return this.pc.removeStream.apply(this.pc, arguments);
};

217
static/js/mediastream/usermedia.js

@ -22,6 +22,11 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -22,6 +22,11 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
// Create AudioContext singleton, if supported.
var context = AudioContext ? new AudioContext() : null;
var peerconnections = {};
// Disabled for now until browser support matures. If enabled this totally breaks
// Firefox and Chrome with Firefox interop.
var enableRenegotiationSupport = false;
var UserMedia = function(options) {
@ -32,9 +37,14 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -32,9 +37,14 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
this.started = false;
this.delay = 0;
this.audioMute = false;
this.videoMute = false;
this.mediaConstraints = null;
// Audio level.
this.audioLevel = 0;
if (!this.options.noaudio && context && context.createScriptProcessor) {
this.audioSource = null;
this.audioProcessor = context.createScriptProcessor(2048, 1, 1);
this.audioProcessor.onaudioprocess = _.bind(function(event) {
@ -54,8 +64,34 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -54,8 +64,34 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
this.audioLevel = rms;
//console.log("this.audioLevel", this.audioLevel);
}, this);
// Connect stream to audio processor if supported.
if (context.createMediaStreamSource) {
this.e.on("localstream", _.bind(function(event, stream) {
if (this.audioSource) {
this.audioSource.disconnect();
}
// Connect to audioProcessor.
this.audioSource = context.createMediaStreamSource(stream);
//console.log("got source", this.audioSource);
this.audioSource.connect(this.audioProcessor);
this.audioProcessor.connect(context.destination);
}, this));
}
}
this.e.on("localstream", _.bind(function(event, stream, oldstream) {
// Update stream support.
if (oldstream) {
_.each(peerconnections, function(pc) {
pc.removeStream(oldstream);
pc.addStream(stream);
console.log("Updated usermedia stream at peer connection", pc, stream);
});
}
}, this));
};
// Static.
@ -112,11 +148,30 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -112,11 +148,30 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
if (!mediaConstraints) {
mediaConstraints = currentcall.mediaConstraints;
}
this.mediaConstraints = mediaConstraints;
return this.doGetUserMediaWithConstraints(mediaConstraints);
};
UserMedia.prototype.doGetUserMediaWithConstraints = function(mediaConstraints) {
if (!mediaConstraints) {
mediaConstraints = this.mediaConstraints;
}
var constraints = $.extend(true, {}, mediaConstraints);
if (this.audioMute) {
constraints.audio = false;
}
if (this.videoMute) {
constraints.video = false;
}
try {
console.log('Requesting access to local media with mediaConstraints:\n' +
' \'' + JSON.stringify(mediaConstraints) + '\'', mediaConstraints);
getUserMedia(mediaConstraints, _.bind(this.onUserMediaSuccess, this), _.bind(this.onUserMediaError, this));
' \'' + JSON.stringify(constraints) + '\'', constraints);
getUserMedia(constraints, _.bind(this.onUserMediaSuccess, this), _.bind(this.onUserMediaError, this));
this.started = true;
return true;
} catch (e) {
@ -134,27 +189,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -134,27 +189,7 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
return;
}
// Get notified of end events.
stream.onended = _.bind(function(event) {
console.log("User media stream ended.");
if (this.started) {
this.stop();
}
}, this);
if (this.audioProcessor && context.createMediaStreamSource) {
// Connect to audioProcessor.
this.audioSource = context.createMediaStreamSource(stream);
//console.log("got source", this.audioSource);
this.audioSource.connect(this.audioProcessor);
this.audioProcessor.connect(context.destination);
}
this.localStream = stream;
// Let webrtc handle the rest.
setTimeout(_.bind(function() {
this.e.triggerHandler("mediasuccess", [this]);
}, this), this.delay);
this.onLocalStream(stream);
};
@ -170,6 +205,36 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -170,6 +205,36 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
};
UserMedia.prototype.onLocalStream = function(stream) {
var oldStream = this.localStream;
if (oldStream) {
oldStream.onended = function() {};
oldStream.stop();
setTimeout(_.bind(function() {
this.e.triggerHandler("mediachanged", [this]);
}, this), 0);
} else {
// Let webrtc handle the rest.
setTimeout(_.bind(function() {
this.e.triggerHandler("mediasuccess", [this]);
}, this), this.delay);
}
// Get notified of end events.
stream.onended = _.bind(function(event) {
console.log("User media stream ended.");
if (this.started) {
this.stop();
}
}, this);
// Set new stream.
this.localStream = stream;
this.e.triggerHandler("localstream", [stream, oldStream, this]);
};
UserMedia.prototype.stop = function() {
this.started = false;
@ -186,6 +251,9 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -186,6 +251,9 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
this.audioProcessor.disconnect()
}
this.audioLevel = 0;
this.audioMute = false;
this.videoMute = false;
this.mediaConstraints = null;
console.log("Stopped user media.");
this.e.triggerHandler("stopped", [this]);
@ -198,53 +266,97 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -198,53 +266,97 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
UserMedia.prototype.applyAudioMute = function(mute) {
if (this.localStream) {
var m = !!mute;
var audioTracks = this.localStream.getAudioTracks();
if (audioTracks.length === 0) {
//console.log('No local audio available.');
return;
}
if (!enableRenegotiationSupport) {
// Disable streams only - does not require renegotiation but keeps mic
// active and the stream will transmit silence.
if (this.localStream) {
var audioTracks = this.localStream.getAudioTracks();
if (audioTracks.length === 0) {
//console.log('No local audio available.');
return;
}
for (var i = 0; i < audioTracks.length; i++) {
audioTracks[i].enabled = !mute;
}
if (mute) {
console.log("Local audio muted by disabling audio tracks.");
} else {
console.log("Local audio unmuted by enabling audio tracks.");
}
for (i = 0; i < audioTracks.length; i++) {
audioTracks[i].enabled = !mute;
}
if (mute) {
console.log("Local audio muted.")
} else {
// Remove audio stream, by creating a new stream and doing renegotiation. This
// is the way to go to disable the mic when audio is muted.
if (this.localStream) {
if (this.audioMute !== m) {
this.audioMute = m;
this.doGetUserMediaWithConstraints();
}
} else {
console.log("Local audio unmuted.")
this.audioMute = m;
}
}
return mute;
return m;
};
UserMedia.prototype.applyVideoMute = function(mute) {
if (this.localStream) {
var m = !!mute;
var videoTracks = this.localStream.getVideoTracks();
if (videoTracks.length === 0) {
//console.log('No local video available.');
return;
}
if (!enableRenegotiationSupport) {
// Disable streams only - does not require renegotiation but keeps camera
// active and the stream will transmit black.
if (this.localStream) {
var videoTracks = this.localStream.getVideoTracks();
if (videoTracks.length === 0) {
//console.log('No local video available.');
return;
}
for (var i = 0; i < videoTracks.length; i++) {
videoTracks[i].enabled = !mute;
}
if (mute) {
console.log("Local video muted by disabling video tracks.");
} else {
console.log("Local video unmuted by enabling video tracks.");
}
for (i = 0; i < videoTracks.length; i++) {
videoTracks[i].enabled = !mute;
}
} else {
if (mute) {
console.log("Local video muted.")
// Removevideo stream, by creating a new stream and doing renegotiation. This
// is the way to go to disable the camera when video is muted.
if (this.localStream) {
if (this.videoMute !== m) {
this.videoMute = m;
this.doGetUserMediaWithConstraints();
}
} else {
console.log("Local video unmuted.")
this.videoMute = m;
}
}
return mute;
return m;
};
@ -253,6 +365,13 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -253,6 +365,13 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
console.log("Add usermedia stream to peer connection", pc, this.localStream);
if (this.localStream) {
pc.addStream(this.localStream);
var id = pc.id;
if (!peerconnections.hasOwnProperty(id)) {
peerconnections[id] = pc;
pc.currentcall.e.one("closed", function() {
delete peerconnections[id];
});
}
}
};
@ -262,13 +381,15 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _ @@ -262,13 +381,15 @@ define(['jquery', 'underscore', 'audiocontext', 'webrtc.adapter'], function($, _
console.log("Remove usermedia stream from peer connection", pc, this.localStream);
if (this.localStream) {
pc.removeStream(this.localStream);
if (peerconnections.hasOwnProperty(pc.id)) {
delete peerconnections[pc.id];
}
}
};
UserMedia.prototype.attachMediaStream = function(video) {
//console.log("attach", video, this.localStream);
attachMediaStream(video, this.localStream);
};

104
static/js/mediastream/webrtc.js

@ -117,6 +117,10 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -117,6 +117,10 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
// Start always, no matter what.
this.maybeStart();
}, this));
this.usermedia.e.on("mediachanged", _.bind(function() {
// Propagate media change events.
this.e.triggerHandler("usermedia", [this.usermedia]);
}, this));
};
@ -226,32 +230,36 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -226,32 +230,36 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
switch (type) {
case "Offer":
var busy = false;
var conference = null;
if (this.currentcall.from !== from) {
console.log("Offer process.");
if (this.settings.stereo) {
data.sdp = utils.addStereo(data.sdp);
}
targetcall = this.findTargetCall(from);
if (targetcall) {
// Hey we know this call.
targetcall.setRemoteDescription(new RTCSessionDescription(data), _.bind(function(sessionDescription, currentcall) {
if (currentcall === this.currentcall) {
// Main call.
this.e.triggerHandler("peercall", [this.currentcall]);
}
currentcall.createAnswer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending answer", sessionDescription, currentcall.id);
this.api.sendAnswer(currentcall.id, sessionDescription);
}, this));
}, this));
} else {
// No target call. Check conference auto answer support.
if (this.currentconference && this.currentconference.id === data._conference) {
console.log("Received conference Offer -> auto.", from, data._conference);
conference = data._conference;
// clean own internal data before feeding into browser.
// Clean own internal data before feeding into browser.
delete data._conference;
} else {
console.log("Received Offer from unknown id -> busy.", from, this.currentconference);
busy = true;
this.currentconference.autoAnswer(from, new RTCSessionDescription(data));
break;
}
}
if (busy) {
// Cannot do anything with this offer, reply with busy.
console.log("Received Offer from unknown id -> busy.", from);
this.api.sendBye(from, "busy");
this.e.triggerHandler("busy", [from, to2, to]);
return;
}
console.log("Offer process.");
if (this.settings.stereo) {
data.sdp = utils.addStereo(data.sdp);
}
if (conference) {
this.currentconference.autoAnswer(from, new RTCSessionDescription(data));
} else {
this.currentcall.setRemoteDescription(new RTCSessionDescription(data), _.bind(this.doAnswer, this));
}
break;
case "Candidate":
@ -280,7 +288,10 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -280,7 +288,10 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
}
// TODO(longsleep): In case of negotiation this could switch offer and answer
// and result in a offer sdp sent as answer data. We need to handle this.
targetcall.setRemoteDescription(new RTCSessionDescription(data));
targetcall.setRemoteDescription(new RTCSessionDescription(data), function() {
// Received remote description as answer.
console.log("Received answer after we sent offer", data);
});
break;
case "Bye":
targetcall = this.findTargetCall(from);
@ -425,16 +436,6 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -425,16 +436,6 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
};
WebRTC.prototype.doAnswer = function() {
this.e.triggerHandler("peercall", [this.currentcall]);
this.currentcall.createAnswer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending answer", sessionDescription, currentcall.id);
this.api.sendAnswer(currentcall.id, sessionDescription);
}, this));
};
WebRTC.prototype.doXfer = function(id, token, options) {
var registeredToken = tokens.get(token);
@ -482,12 +483,17 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -482,12 +483,17 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
// Connect.
xfer.setInitiate(true);
xfer.createPeerConnection();
xfer.createPeerConnection(_.bind(function() {
xfer.e.on("negotiationNeeded", _.bind(function(event, currentxfer) {
this.sendOfferWhenNegotiationNeeded(currentxfer, id);
}, this));
}, this));
/*
xfer.createOffer(_.bind(function(sessionDescription, currentxfer) {
console.log("Sending xfer offer with sessionDescription", sessionDescription, currentxfer.id);
// TODO(longsleep): Support sending this through data channel too if we have one.
this.api.sendOffer(id, sessionDescription);
}, this));
}, this));*/
};
@ -553,12 +559,17 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -553,12 +559,17 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
// Connect.
peerscreenshare.setInitiate(true); //XXX(longsleep): This creates a data channel which is not needed.
peerscreenshare.createPeerConnection();
peerscreenshare.createPeerConnection(_.bind(function() {
peerscreenshare.e.on("negotiationNeeded", _.bind(function(event, currentscreenshare) {
this.sendOfferWhenNegotiationNeeded(currentscreenshare, id);
}, this));
}, this));
/*
peerscreenshare.createOffer(_.bind(function(sessionDescription, currentscreenshare) {
console.log("Sending screen share offer with sessionDescription", sessionDescription, currentscreenshare.id);
// TODO(longsleep): Support sending this through data channel too if we have one.
this.api.sendOffer(id, sessionDescription);
}, this));
}, this));*/
};
@ -637,13 +648,16 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -637,13 +648,16 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
}
this.started = true;
if (this.initiator) {
currentcall.createOffer(_.bind(function(sessionDescription, currentcall) {
/*currentcall.createOffer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending offer with sessionDescription", sessionDescription, currentcall.id);
this.api.sendOffer(currentcall.id, sessionDescription);
}, this));
}, this));*/
} else {
this.calleeStart();
}
currentcall.e.on("negotiationNeeded", _.bind(function(event, currentcall) {
this.sendOfferWhenNegotiationNeeded(currentcall);
}, this));
}, this), _.bind(function() {
// Error call.
this.e.triggerHandler("error", ["Failed to create peer connection. See log for details."]);
@ -664,6 +678,22 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -664,6 +678,22 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
};
WebRTC.prototype.sendOfferWhenNegotiationNeeded = function(currentcall, to) {
// TODO(longsleep): Check if the check for stable is really required.
if (currentcall.peerconnection.pc.signalingState === "stable") {
if (!to) {
to = currentcall.id;
}
currentcall.createOffer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending offer with sessionDescription", sessionDescription, to, currentcall);
// TODO(longsleep): Support sending this through data channel too if we have one.
this.api.sendOffer(to, sessionDescription);
}, this));
}
};
WebRTC.prototype.onConnectionStateChange = function(iceConnectionState, currentcall) {
// Defer this to allow native event handlers to complete before running more stuff.
_.defer(_.bind(function() {

28
static/js/services/videolayout.js

@ -23,14 +23,14 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -23,14 +23,14 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
var dynamicCSSContainer = "audiovideo-dynamic";
var renderers = {};
var getRemoteVideoSize = function(videos, peers) {
var getRemoteVideoSize = function(videos, streams) {
var size = {
width: 1920,
height: 1080
}
if (videos.length) {
if (videos.length === 1) {
var remoteVideo = peers[videos[0]].element.find("video").get(0);
var remoteVideo = streams[videos[0]].element.find("video").get(0);
if (remoteVideo) {
size.width = remoteVideo.videoWidth;
size.height = remoteVideo.videoHeight;
@ -51,7 +51,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -51,7 +51,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
OnePeople.prototype.name = "onepeople";
OnePeople.prototype.render = function(container, size, scope, videos, peers) {
OnePeople.prototype.render = function(container, size, scope, videos, streams) {
if (this.closed) {
return;
@ -61,7 +61,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -61,7 +61,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
var videoHeight;
if (videos.length) {
var remoteSize = getRemoteVideoSize(videos, peers);
var remoteSize = getRemoteVideoSize(videos, streams);
videoWidth = remoteSize.width;
videoHeight = remoteSize.height;
}
@ -235,25 +235,25 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -235,25 +235,25 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
};
ConferenceKiosk.prototype.render = function(container, size, scope, videos, peers) {
ConferenceKiosk.prototype.render = function(container, size, scope, videos, streams) {
var big = this.big;
if (big) {
var currentbigpeerid = this.big.data("peerid");
if (!peers[currentbigpeerid]) {
if (!streams[currentbigpeerid]) {
console.log("Current big peer is no longer there", currentbigpeerid);
this.big = big = null;
}
}
if (!big) {
if (videos.length) {
this.makeBig(peers[videos[0]].element);
this.makeBig(streams[videos[0]].element);
this.bigVideo.style.opacity = 1;
}
}
var remoteSize = getRemoteVideoSize(videos, peers);
var remoteSize = getRemoteVideoSize(videos, streams);
var aspectRatio = remoteSize.width / remoteSize.height;
var innerHeight = size.height - 110;
var innerWidth = size.width;
@ -304,18 +304,18 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -304,18 +304,18 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
Classroom.prototype = Object.create(ConferenceKiosk.prototype);
Classroom.prototype.constructor = Classroom;
Classroom.prototype.name = "classroom";
Classroom.prototype.render = function(container, size, scope, videos, peers) {
Classroom.prototype.render = function(container, size, scope, videos, streams) {
var big = this.big;
if (big) {
var currentbigpeerid = this.big.data("peerid");
if (!peers[currentbigpeerid]) {
if (!streams[currentbigpeerid]) {
console.log("Current big peer is no longer there", currentbigpeerid);
this.big = big = null;
}
}
if (!big) {
if (videos.length) {
this.makeBig(peers[videos[0]].element);
this.makeBig(streams[videos[0]].element);
this.bigVideo.style.opacity = 1;
}
@ -345,8 +345,8 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -345,8 +345,8 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
return r;
};
var videos = _.keys(controller.peers);
var peers = controller.peers;
var videos = _.keys(controller.streams);
var streams = controller.streams;
var container = scope.container;
var layoutparent = scope.layoutparent;
@ -370,7 +370,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern @@ -370,7 +370,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
}
}
return current.render(container, size, scope, videos, peers);
return current.render(container, size, scope, videos, streams);
},
register: function(name, impl) {

2
static/partials/audiovideopeer.html

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
<div class="remoteVideo" ng-class="{'withvideo': withvideo, 'onlyaudio': onlyaudio, 'talking': talking}">
<div class="remoteVideo" ng-class="{'withvideo': withvideo, 'onlyaudio': onlyaudio, 'talking': peersTalking[peerid]}">
<video autoplay="autoplay"></video>
<div class="peerLabel">{{peerid|displayName}}</div>
<div class="peerActions">

Loading…
Cancel
Save