Browse Source

Merge 58dfe318e8 into 7c6dcd9633

pull/138/merge
Simon Eisenmann 12 years ago
parent
commit
9b4a8733ed
  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