Handles audio and video streams separately. Minor fixes.

This commit is contained in:
paweldomas 2014-03-12 16:08:24 +01:00
parent 8b96d134e3
commit 9fd03e1bef
6 changed files with 168 additions and 74 deletions

139
app.js
View File

@ -49,11 +49,10 @@ function init() {
if (config.useStunTurn) {
connection.jingle.getStunAndTurnCredentials();
}
if (RTC.browser === 'firefox') {
getUserMediaWithConstraints(['audio']);
} else {
getUserMediaWithConstraints(['audio', 'video'], config.resolution || '360');
}
getUserMediaWithConstraints( ['audio'], audioStreamReady,
function(error){
console.error('failed to obtain audio stream - stop', error);
});
document.getElementById('connect').disabled = true;
} else {
console.log('status', status);
@ -61,6 +60,31 @@ function init() {
});
}
function audioStreamReady(stream) {
change_local_audio(stream);
if(RTC.browser !== 'firefox') {
getUserMediaWithConstraints( ['video'], videoStreamReady, videoStreamFailed, config.resolution || '360' );
} else {
doJoin();
}
}
function videoStreamReady(stream) {
change_local_video(stream);
doJoin();
}
function videoStreamFailed(error) {
console.warn("Failed to obtain video stream - continue anyway", error);
doJoin();
}
function doJoin() {
var roomnode = null;
var path = window.location.pathname;
@ -104,8 +128,17 @@ function doJoin() {
connection.emuc.doJoin(roomjid);
}
$(document).bind('mediaready.jingle', function (event, stream) {
connection.jingle.localStream = stream;
function change_local_audio(stream) {
connection.jingle.localAudio = stream;
RTC.attachMediaStream($('#localAudio'), stream);
document.getElementById('localAudio').autoplay = true;
document.getElementById('localAudio').volume = 0;
}
function change_local_video(stream) {
connection.jingle.localVideo = stream;
RTC.attachMediaStream($('#localVideo'), stream);
document.getElementById('localVideo').autoplay = true;
document.getElementById('localVideo').volume = 0;
@ -124,16 +157,14 @@ $(document).bind('mediaready.jingle', function (event, stream) {
}
});
});
doJoin();
});
$(document).bind('mediafailure.jingle', function () {
// FIXME
});
}
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
function waitForRemoteVideo(selector, sid) {
if(selector.removed) {
console.warn("media removed before had started", selector);
return;
}
var sess = connection.jingle.sessions[sid];
if (data.stream.id === 'mixedmslabel') return;
videoTracks = data.stream.getVideoTracks();
@ -188,8 +219,11 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
remotes.appendChild(container);
Util.playSoundNotification('userJoined');
}
var vid = document.createElement('video');
var id = 'remoteVideo_' + sid + '_' + data.stream.id;
var isVideo = data.stream.getVideoTracks().length > 0;
var vid = isVideo ? document.createElement('video') : document.createElement('audio');
var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' + data.stream.id;
vid.id = id;
vid.autoplay = true;
vid.oncontextmenu = function () { return false; };
@ -203,11 +237,14 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
var sel = $('#' + id);
sel.hide();
RTC.attachMediaStream(sel, data.stream);
waitForRemoteVideo(sel, sid);
if(isVideo) {
waitForRemoteVideo(sel, sid);
}
data.stream.onended = function () {
console.log('stream ended', this.id);
var src = $('#' + id).attr('src');
if (src === $('#largeVideo').attr('src')) {
if (sel.attr('src') === $('#largeVideo').attr('src')) {
// this is currently displayed as large
// pick the last visible video in the row
// if nobody else is left, this picks the local video
@ -221,9 +258,21 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
updateLargeVideo(pick.src, isLocalVideo, pick.volume);
}
}
$('#' + id).parent().remove();
Util.playSoundNotification('userLeft');
resizeThumbnails();
// Mark video as removed to cancel waiting loop(if video is removed before has started)
sel.removed = true;
var userContainer = sel.parent();
if(userContainer.children().length === 0) {
console.log("Remove whole user");
// Remove whole container
userContainer.remove();
Util.playSoundNotification('userLeft');
resizeThumbnails();
} else {
// Remove only stream holder
sel.remove();
console.log("Remove stream only", sel);
}
};
sel.click(
function () {
@ -232,9 +281,10 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
}
);
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
if (data.peerjid && sess.peerjid === data.peerjid &&
data.stream.getVideoTracks().length === 0 &&
connection.jingle.localStream.getVideoTracks().length > 0) {
if (isVideo
&& data.peerjid && sess.peerjid === data.peerjid &&
data.stream.getVideoTracks().length === 0 &&
connection.jingle.localVideo.getVideoTracks().length > 0) {
window.setTimeout(function() {
sendKeyframe(sess.peerconnection);
}, 3000);
@ -379,7 +429,12 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
var ssrc = SDPUtil.find_line(media, 'a=ssrc:').substring(7).split(' ')[0];
newssrcs[type] = ssrc;
directions[type] = (SDPUtil.find_line(media, 'a=sendrecv') || SDPUtil.find_line(media, 'a=recvonly') || SDPUtil.find_line('a=sendonly') || SDPUtil.find_line('a=inactive') || 'a=sendrecv').substr(2);
directions[type] = (
SDPUtil.find_line(media, 'a=sendrecv')
|| SDPUtil.find_line(media, 'a=recvonly')
|| SDPUtil.find_line('a=sendonly')
|| SDPUtil.find_line('a=inactive')
|| 'a=sendrecv' ).substr(2);
}
});
console.log('new ssrcs', newssrcs);
@ -422,7 +477,7 @@ $(document).bind('joined.muc', function (event, jid, info) {
$(document).bind('entered.muc', function (event, jid, info, pres) {
console.log('entered', jid, info);
console.log(focus);
console.log('is focus?' + focus ? 'true' : 'false');
var videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
var container = addRemoteVideoContainer(videoSpanId);
@ -712,17 +767,22 @@ function updateLargeVideo(newSrc, localVideo, vol) {
}
}
function getConferenceHandler() {
return focus ? focus : activecall;
}
function toggleVideo() {
if (!(connection && connection.jingle.localStream)) return;
if (!(connection && connection.jingle.localVideo)) return;
var ismuted = false;
for (var idx = 0; idx < connection.jingle.localStream.getVideoTracks().length; idx++) {
ismuted = !connection.jingle.localStream.getVideoTracks()[idx].enabled;
var localVideo = connection.jingle.localVideo;
for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) {
ismuted = !localVideo.getVideoTracks()[idx].enabled;
}
for (var idx = 0; idx < connection.jingle.localStream.getVideoTracks().length; idx++) {
connection.jingle.localStream.getVideoTracks()[idx].enabled = !connection.jingle.localStream.getVideoTracks()[idx].enabled;
for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) {
localVideo.getVideoTracks()[idx].enabled = !localVideo.getVideoTracks()[idx].enabled;
}
var sess = focus || activecall;
if (!sess) {
var sess = getConferenceHandler();
if (sess) {
return;
}
sess.peerconnection.pendingop = ismuted ? 'unmute' : 'mute';
@ -730,9 +790,10 @@ function toggleVideo() {
}
function toggleAudio() {
if (!(connection && connection.jingle.localStream)) return;
for (var idx = 0; idx < connection.jingle.localStream.getAudioTracks().length; idx++) {
connection.jingle.localStream.getAudioTracks()[idx].enabled = !connection.jingle.localStream.getAudioTracks()[idx].enabled;
if (!(connection && connection.jingle.localAudio)) return;
var localAudio = connection.jingle.localAudio;
for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
localAudio.getAudioTracks()[idx].enabled = !localAudio.getAudioTracks()[idx].enabled;
}
}
@ -1170,6 +1231,10 @@ function showFocusIndicator() {
var session = connection.jingle.sessions[Object.keys(connection.jingle.sessions)[0]];
var focusId = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
var focusContainer = document.getElementById(focusId);
if(!focusContainer) {
console.error("No focus container!");
return;
}
var indicatorSpan = $('#' + focusId + ' .focusindicator');
if (!indicatorSpan || indicatorSpan.length === 0) {

View File

@ -79,6 +79,7 @@
<span id="localVideoContainer" class="videocontainer">
<span id="localNick"></span>
<video id="localVideo" autoplay oncontextmenu="return false;" muted></video>
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
<span class="focusindicator"></span>
</span>
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
@ -99,6 +100,7 @@
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
</div>
<a id="downloadlog" onclick='dump(event.target);'><i title="Download support information" class="fa fa-cloud-download"></i></a>
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
@ -106,5 +108,6 @@
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
</body>
</html>

View File

@ -77,24 +77,29 @@ ColibriFocus.prototype.makeConference = function (peers) {
self.channels.push([]);
});
this.peerconnection.addStream(this.connection.jingle.localStream);
if(connection.jingle.localAudio) {
this.peerconnection.addStream(connection.jingle.localAudio);
}
if(connection.jingle.localVideo) {
this.peerconnection.addStream(connection.jingle.localVideo);
}
this.peerconnection.oniceconnectionstatechange = function (event) {
console.warn('ice connection state changed to', self.peerconnection.iceConnectionState);
/*
if (self.peerconnection.signalingState == 'stable' && self.peerconnection.iceConnectionState == 'connected') {
console.log('adding new remote SSRCs from iceconnectionstatechange');
window.setTimeout(function() { self.modifySources(); }, 1000);
}
*/
if (self.peerconnection.signalingState == 'stable' && self.peerconnection.iceConnectionState == 'connected') {
console.log('adding new remote SSRCs from iceconnectionstatechange');
window.setTimeout(function() { self.modifySources(); }, 1000);
}
*/
};
this.peerconnection.onsignalingstatechange = function (event) {
console.warn(self.peerconnection.signalingState);
/*
if (self.peerconnection.signalingState == 'stable' && self.peerconnection.iceConnectionState == 'connected') {
console.log('adding new remote SSRCs from signalingstatechange');
window.setTimeout(function() { self.modifySources(); }, 1000);
}
*/
if (self.peerconnection.signalingState == 'stable' && self.peerconnection.iceConnectionState == 'connected') {
console.log('adding new remote SSRCs from signalingstatechange');
window.setTimeout(function() { self.modifySources(); }, 1000);
}
*/
};
this.peerconnection.onaddstream = function (event) {
self.remoteStream = event.stream;
@ -146,7 +151,6 @@ ColibriFocus.prototype._makeConference = function () {
var elem = $iq({to: this.bridgejid, type: 'get'});
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri'});
var stream = this.connection.jingle.localStream;
this.media.forEach(function (name) {
elem.c('content', {name: name});
elem.c('channel', {initiator: 'true', expire: '15'}).up();
@ -443,7 +447,8 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
this.connection);
sess.initiate(peer);
sess.colibri = this;
sess.localStream = this.connection.jingle.localStream;
// We do not announce our audio per conference peer, so only video is set here
sess.localVideo = this.connection.jingle.localVideo;
sess.media_constraints = this.connection.jingle.media_constraints;
sess.pc_constraints = this.connection.jingle.pc_constraints;
sess.ice_config = this.connection.jingle.ice_config;
@ -610,7 +615,7 @@ ColibriFocus.prototype.sendSSRCUpdate = function (sdp, jid, isadd) {
console.warn('got modify result');
},
function (err) {
console.warn('got modify error');
console.warn('got modify error', err);
}
);
} else {
@ -697,7 +702,7 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
console.log('got result');
},
function (err) {
console.warn('got error');
console.error('got error', err);
}
);
};
@ -748,7 +753,7 @@ ColibriFocus.prototype.sendIceCandidates = function (candidates) {
console.log('got result');
},
function (err) {
console.warn('got error');
console.error('got error', err);
}
);
};
@ -779,7 +784,7 @@ ColibriFocus.prototype.terminate = function (session, reason) {
console.log('got result');
},
function (err) {
console.log('got error');
console.error('got error', err);
}
);
// and remove from channels
@ -811,7 +816,7 @@ ColibriFocus.prototype.hardMuteVideo = function (muted) {
this.peerconnection.hardMuteVideo(muted);
this.connection.jingle.localStream.getVideoTracks().forEach(function (track) {
this.connection.jingle.localVideo.getVideoTracks().forEach(function (track) {
track.enabled = !muted;
});
};

View File

@ -220,7 +220,6 @@ TraceablePeerConnection.prototype.addSource = function (elem) {
});
sdp.raw = sdp.session + sdp.media.join('');
});
this.modifySources();
};
TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLines) {
@ -257,7 +256,6 @@ TraceablePeerConnection.prototype.removeSource = function (elem) {
});
sdp.raw = sdp.session + sdp.media.join('');
});
this.modifySources();
};
TraceablePeerConnection.prototype.modifySources = function(successCallback) {
@ -275,11 +273,11 @@ TraceablePeerConnection.prototype.modifySources = function(successCallback) {
if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
this.wait = true;
window.setTimeout(function() { self.modifySources(); }, 250);
window.setTimeout(function() { self.modifySources(successCallback); }, 250);
return;
}
if (this.wait) {
window.setTimeout(function() { self.modifySources(); }, 2500);
window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
this.wait = false;
return;
}
@ -325,6 +323,12 @@ TraceablePeerConnection.prototype.modifySources = function(successCallback) {
self.pendingop = null;
}
// FIXME: pushing down an answer while ice connection state
// is still checking is bad...
//console.log(self.peerconnection.iceConnectionState);
// trying to work around another chrome bug
//modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
self.setLocalDescription(modifiedAnswer,
function() {
//console.log('modified setLocalDescription ok');
@ -471,7 +475,7 @@ function setupRTC() {
return RTC;
}
function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps) {
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
@ -553,14 +557,18 @@ function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
RTC.getUserMedia(constraints,
function (stream) {
console.log('onUserMediaSuccess');
$(document).trigger('mediaready.jingle', [stream]);
success_callback(stream);
},
function (error) {
console.warn('Failed to get access to local media. Error ', error);
$(document).trigger('mediafailure.jingle');
if(failure_callback) {
failure_callback(error);
}
});
} catch (e) {
console.error('GUM failed: ', e);
$(document).trigger('mediafailure.jingle');
if(failure_callback) {
failure_callback(e);
}
}
}

View File

@ -12,7 +12,8 @@ Strophe.addConnectionPlugin('jingle', {
}
// MozDontOfferDataChannel: true when this is firefox
},
localStream: null,
localAudio: null,
localVideo: null,
init: function (conn) {
this.connection = conn;
@ -38,12 +39,13 @@ Strophe.addConnectionPlugin('jingle', {
onJingle: function (iq) {
var sid = $(iq).find('jingle').attr('sid');
var action = $(iq).find('jingle').attr('action');
var fromJid = iq.getAttribute('from');
// send ack first
var ack = $iq({type: 'result',
to: iq.getAttribute('from'),
to: fromJid,
id: iq.getAttribute('id')
});
console.log('on jingle ' + action);
console.log('on jingle ' + action + ' from ' + fromJid, iq);
var sess = this.sessions[sid];
if ('session-initiate' != action) {
if (sess === null) {
@ -56,8 +58,8 @@ Strophe.addConnectionPlugin('jingle', {
}
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
// local jid is not checked
if (Strophe.getBareJidFromJid(iq.getAttribute('from')) != Strophe.getBareJidFromJid(sess.peerjid)) {
console.warn('jid mismatch for session id', sid, iq.getAttribute('from'), sess.peerjid);
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
ack.type = 'error';
ack.c('error', {type: 'cancel'})
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
@ -82,14 +84,17 @@ Strophe.addConnectionPlugin('jingle', {
case 'session-initiate':
sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
// configure session
if (this.localStream) {
sess.localStreams.push(this.localStream);
if (this.localAudio) {
sess.localStreams.push(this.localAudio);
}
if (this.localVideo) {
sess.localStreams.push(this.localVideo);
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate($(iq).attr('from'), false);
sess.initiate(fromJid, false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
@ -152,8 +157,11 @@ Strophe.addConnectionPlugin('jingle', {
Math.random().toString(36).substr(2, 12), // random string
this.connection);
// configure session
if (this.localStream) {
sess.localStreams.push(this.localStream);
if (this.localAudio) {
sess.localStreams.push(this.localAudio);
}
if (this.localVideo) {
sess.localStreams.push(this.localVideo);
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;

View File

@ -76,6 +76,7 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
this.peerconnection.onremovestream = function (event) {
self.remoteStream = null;
// FIXME: remove from this.remoteStreams
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
};
this.peerconnection.onsignalingstatechange = function (event) {
@ -635,11 +636,15 @@ JingleSession.prototype.sendTerminate = function (reason, text) {
JingleSession.prototype.addSource = function (elem) {
this.peerconnection.addSource(elem);
this.modifySources();
};
JingleSession.prototype.removeSource = function (elem) {
this.peerconnection.removeSource(elem);
this.modifySources();
};
JingleSession.prototype.modifySources = function() {
@ -655,7 +660,7 @@ JingleSession.prototype.hardMuteVideo = function (muted) {
this.peerconnection.hardMuteVideo(muted);
this.connection.jingle.localStream.getVideoTracks().forEach(function (track) {
this.connection.jingle.localVideo.getVideoTracks().forEach(function (track) {
track.enabled = !muted;
});
};