Fixes flickering issue with simulcast.

This commit is contained in:
George Politis 2014-10-06 13:35:22 +02:00
parent da1e4183e5
commit 71c08450bb
3 changed files with 113 additions and 106 deletions

View File

@ -26,6 +26,5 @@ var config = {
enableRecording: false,
enableWelcomePage: false,
enableSimulcast: false,
useNativeSimulcast: false,
isBrand: false
};

View File

@ -8,7 +8,14 @@ function Simulcast() {
"use strict";
// TODO(gp) split the Simulcast class in two classes : NativeSimulcast and ClassicSimulcast.
this.debugLvl = 1;
// Once we properly support native simulcast, enable it automatically in the
// supported browsers (Chrome).
this.useNativeSimulcast = false;
// TODO(gp) we need a logging framework for javascript à la log4j or the
// java logging framework that allows for selective log display
this.debugLvl = 0;
}
(function () {
@ -446,7 +453,7 @@ function Simulcast() {
* @returns {*}
*/
Simulcast.prototype.transformAnswer = function (desc) {
if (config.enableSimulcast && config.useNativeSimulcast) {
if (config.enableSimulcast && this.useNativeSimulcast) {
var sb = desc.sdp.split('\r\n');
@ -523,7 +530,7 @@ function Simulcast() {
if (config.enableSimulcast) {
if (config.useNativeSimulcast) {
if (this.useNativeSimulcast) {
sb = desc.sdp.split('\r\n');
this._explodeLocalSimulcastSources(sb);
@ -595,7 +602,7 @@ function Simulcast() {
* @returns {*}
*/
Simulcast.prototype.transformLocalDescription = function (desc) {
if (config.enableSimulcast && !config.useNativeSimulcast) {
if (config.enableSimulcast && !this.useNativeSimulcast) {
var sb = desc.sdp.split('\r\n');
@ -632,7 +639,7 @@ function Simulcast() {
this._cacheRemoteVideoSources(sb);
this._removeSimulcastGroup(sb); // NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
if (config.useNativeSimulcast) {
if (this.useNativeSimulcast) {
// We don't need the goog conference flag if we're not doing
// native simulcast.
this._ensureGoogConference(sb);
@ -700,7 +707,7 @@ function Simulcast() {
: stream;
};
var stream;
var localStream, displayedLocalVideoStream;
/**
* GUM for simulcast.
@ -727,7 +734,7 @@ function Simulcast() {
console.log('HQ constraints: ', constraints);
console.log('LQ constraints: ', lqConstraints);
if (config.enableSimulcast && !config.useNativeSimulcast) {
if (config.enableSimulcast && !this.useNativeSimulcast) {
// NOTE(gp) if we request the lq stream first webkitGetUserMedia
// fails randomly. Tested with Chrome 37. As fippo suggested, the
@ -736,6 +743,8 @@ function Simulcast() {
navigator.webkitGetUserMedia(constraints, function (hqStream) {
localStream = hqStream;
// reset local maps.
localMaps.msids = [];
localMaps.msid2ssrc = {};
@ -745,6 +754,8 @@ function Simulcast() {
navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
displayedLocalVideoStream = lqStream;
// NOTE(gp) The specification says Array.forEach() will visit
// the array elements in numeric order, and that it doesn't
// visit elements that don't exist.
@ -752,9 +763,8 @@ function Simulcast() {
// add lq trackid to local map
localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
hqStream.addTrack(lqStream.getVideoTracks()[0]);
stream = hqStream;
success(hqStream);
localStream.addTrack(lqStream.getVideoTracks()[0]);
success(localStream);
}, err);
}, err);
} else {
@ -769,8 +779,8 @@ function Simulcast() {
// add hq stream to local map
localMaps.msids.push(hqStream.getVideoTracks()[0].id);
stream = hqStream;
success(hqStream);
displayedLocalVideoStream = localStream = hqStream;
success(localStream);
}, err);
}
};
@ -801,7 +811,7 @@ function Simulcast() {
trackid = tid;
return true;
}
}) && stream.getVideoTracks().some(function(track) {
}) && localStream.getVideoTracks().some(function(track) {
// Start/stop the track that corresponds to the track id
if (track.id === trackid) {
track.enabled = enabled;
@ -818,15 +828,7 @@ function Simulcast() {
};
Simulcast.prototype.getLocalVideoStream = function() {
var track;
stream.getVideoTracks().some(function(t) {
if ((track = t).enabled) {
return true;
}
});
return new webkitMediaStream([track]);
return displayedLocalVideoStream;
};
$(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {

View File

@ -3,7 +3,10 @@ var VideoLayout = (function (my) {
var currentDominantSpeaker = null;
var lastNCount = config.channelLastN;
var lastNEndpointsCache = [];
var largeVideoNewSrc = '';
var largeVideoState = {
updateInProgress: false,
newSrc: ''
};
my.changeLocalAudio = function(stream) {
connection.jingle.localAudio = stream;
@ -66,7 +69,9 @@ var VideoLayout = (function (my) {
localVideoSelector.addClass("flipVideoX");
}
// Attach WebRTC stream
RTC.attachMediaStream(localVideoSelector, stream);
var simulcast = new Simulcast();
var videoStream = simulcast.getLocalVideoStream();
RTC.attachMediaStream(localVideoSelector, videoStream);
localVideoSrc = localVideo.src;
@ -114,68 +119,91 @@ var VideoLayout = (function (my) {
console.log('hover in', newSrc);
if ($('#largeVideo').attr('src') != newSrc) {
largeVideoNewSrc = newSrc;
var isVisible = $('#largeVideo').is(':visible');
// Due to the simulcast the localVideoSrc may have changed when the
// fadeOut event triggers. In that case the getJidFromVideoSrc and
// isVideoSrcDesktop methods will not function correctly.
//
// Also, again due to the simulcast, the updateLargeVideo method can
// be called multiple times almost simultaneously. Therefore, we
// store the state here and update only once.
// we need this here because after the fade the videoSrc may have
// changed.
var isDesktop = isVideoSrcDesktop(newSrc);
largeVideoState.newSrc = newSrc;
largeVideoState.isVisible = $('#largeVideo').is(':visible');
largeVideoState.isDesktop = isVideoSrcDesktop(newSrc);
largeVideoState.userJid = getJidFromVideoSrc(newSrc);
var userJid = getJidFromVideoSrc(newSrc);
// we want the notification to trigger even if userJid is undefined,
// or null.
$(document).trigger("selectedendpointchanged", [userJid]);
// Screen stream is already rotated
largeVideoState.flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
$('#largeVideo').fadeOut(300, function () {
var oldSrc = $(this).attr('src');
var oldSrc = $('#largeVideo').attr('src');
largeVideoState.oldJid = getJidFromVideoSrc(oldSrc);
$(this).attr('src', newSrc);
var fade = false;
if (largeVideoState.oldJid != largeVideoState.userJid) {
fade = true;
// we want the notification to trigger even if userJid is undefined,
// or null.
$(document).trigger("selectedendpointchanged", [largeVideoState.userJid]);
}
// Screen stream is already rotated
var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
if (!largeVideoState.updateInProgress) {
largeVideoState.updateInProgress = true;
var videoTransform = document.getElementById('largeVideo')
.style.webkitTransform;
var doUpdate = function () {
if (flipX && videoTransform !== 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform
= "scaleX(-1)";
}
else if (!flipX && videoTransform === 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform
= "none";
}
$('#largeVideo').attr('src', largeVideoState.newSrc);
// Change the way we'll be measuring and positioning large video
var videoTransform = document.getElementById('largeVideo')
.style.webkitTransform;
getVideoSize = isDesktop
? getDesktopVideoSize
: getCameraVideoSize;
getVideoPosition = isDesktop
? getDesktopVideoPosition
: getCameraVideoPosition;
if (isVisible) {
// Only if the large video is currently visible.
// Disable previous dominant speaker video.
var oldJid = getJidFromVideoSrc(oldSrc);
if (oldJid) {
var oldResourceJid = Strophe.getResourceFromJid(oldJid);
VideoLayout.enableDominantSpeaker(oldResourceJid, false);
if (largeVideoState.flipX && videoTransform !== 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform
= "scaleX(-1)";
}
else if (!largeVideoState.flipX && videoTransform === 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform
= "none";
}
// Enable new dominant speaker in the remote videos section.
var userJid = getJidFromVideoSrc(newSrc);
if (userJid)
{
var resourceJid = Strophe.getResourceFromJid(userJid);
VideoLayout.enableDominantSpeaker(resourceJid, true);
}
// Change the way we'll be measuring and positioning large video
$(this).fadeIn(300);
getVideoSize = largeVideoState.isDesktop
? getDesktopVideoSize
: getCameraVideoSize;
getVideoPosition = largeVideoState.isDesktop
? getDesktopVideoPosition
: getCameraVideoPosition;
if (largeVideoState.isVisible) {
// 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);
}
// Enable new dominant speaker in the remote videos section.
if (largeVideoState.userJid) {
var resourceJid = Strophe.getResourceFromJid(largeVideoState.userJid);
VideoLayout.enableDominantSpeaker(resourceJid, true);
}
largeVideoState.updateInProgress = false;
if (fade) {
// using "this" should be ok because we're called
// from within the fadeOut event.
$(this).fadeIn(300);
}
}
};
if (fade) {
$('#largeVideo').fadeOut(300, doUpdate);
} else {
doUpdate();
}
});
}
}
};
@ -814,15 +842,6 @@ var VideoLayout = (function (my) {
* disabled
*/
my.enableDominantSpeaker = function(resourceJid, isEnable) {
var displayName = resourceJid;
var nameSpan = $('#participant_' + resourceJid + '>span.displayname');
if (nameSpan.length > 0)
displayName = nameSpan.text();
console.log("UI enable dominant speaker",
displayName,
resourceJid,
isEnable);
var videoSpanId = null;
var videoContainerId = null;
@ -836,6 +855,16 @@ var VideoLayout = (function (my) {
videoContainerId = videoSpanId;
}
var displayName = resourceJid;
var nameSpan = $('#' + videoContainerId + '>span.displayname');
if (nameSpan.length > 0)
displayName = nameSpan.text();
console.log("UI enable dominant speaker",
displayName,
resourceJid,
isEnable);
videoSpan = document.getElementById(videoContainerId);
if (!videoSpan) {
@ -1322,29 +1351,6 @@ var VideoLayout = (function (my) {
}
});
$(document).bind('simulcastlayerstarted simulcastlayerstopped', function(event) {
var localVideoSelector = $('#' + 'localVideo_' + connection.jingle.localVideo.id);
var simulcast = new Simulcast();
var stream = simulcast.getLocalVideoStream();
var updateLargeVideo = (connection.emuc.myroomjid
== getJidFromVideoSrc(largeVideoNewSrc));
var updateFocusedVideoSrc = (localVideoSrc == focusedVideoSrc);
// Attach WebRTC stream
RTC.attachMediaStream(localVideoSelector, stream);
localVideoSrc = $(localVideoSelector).attr('src');
if (updateLargeVideo) {
VideoLayout.updateLargeVideo(localVideoSrc);
}
if (updateFocusedVideoSrc) {
focusedVideoSrc = localVideoSrc;
}
});
/**
* On simulcast layers changed event.
*/
@ -1402,7 +1408,7 @@ var VideoLayout = (function (my) {
var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join(''));
var updateLargeVideo = (ssrc2jid[videoSrcToSsrc[selRemoteVideo.attr('src')]]
== ssrc2jid[videoSrcToSsrc[largeVideoNewSrc]]);
== ssrc2jid[videoSrcToSsrc[largeVideoState.newSrc]]);
var updateFocusedVideoSrc = (selRemoteVideo.attr('src') == focusedVideoSrc);
var electedStreamUrl = webkitURL.createObjectURL(electedStream);