Fixes flickering issue with simulcast.
This commit is contained in:
parent
da1e4183e5
commit
71c08450bb
|
@ -26,6 +26,5 @@ var config = {
|
|||
enableRecording: false,
|
||||
enableWelcomePage: false,
|
||||
enableSimulcast: false,
|
||||
useNativeSimulcast: false,
|
||||
isBrand: false
|
||||
};
|
||||
|
|
46
simulcast.js
46
simulcast.js
|
@ -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) {
|
||||
|
|
134
videolayout.js
134
videolayout.js
|
@ -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);
|
||||
|
||||
var userJid = getJidFromVideoSrc(newSrc);
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
$(document).trigger("selectedendpointchanged", [userJid]);
|
||||
|
||||
$('#largeVideo').fadeOut(300, function () {
|
||||
var oldSrc = $(this).attr('src');
|
||||
|
||||
$(this).attr('src', newSrc);
|
||||
largeVideoState.newSrc = newSrc;
|
||||
largeVideoState.isVisible = $('#largeVideo').is(':visible');
|
||||
largeVideoState.isDesktop = isVideoSrcDesktop(newSrc);
|
||||
largeVideoState.userJid = getJidFromVideoSrc(newSrc);
|
||||
|
||||
// Screen stream is already rotated
|
||||
var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
|
||||
largeVideoState.flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
|
||||
|
||||
var oldSrc = $('#largeVideo').attr('src');
|
||||
largeVideoState.oldJid = getJidFromVideoSrc(oldSrc);
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
if (!largeVideoState.updateInProgress) {
|
||||
largeVideoState.updateInProgress = true;
|
||||
|
||||
var doUpdate = function () {
|
||||
|
||||
$('#largeVideo').attr('src', largeVideoState.newSrc);
|
||||
|
||||
var videoTransform = document.getElementById('largeVideo')
|
||||
.style.webkitTransform;
|
||||
|
||||
if (flipX && videoTransform !== 'scaleX(-1)') {
|
||||
if (largeVideoState.flipX && videoTransform !== 'scaleX(-1)') {
|
||||
document.getElementById('largeVideo').style.webkitTransform
|
||||
= "scaleX(-1)";
|
||||
}
|
||||
else if (!flipX && videoTransform === 'scaleX(-1)') {
|
||||
else if (!largeVideoState.flipX && videoTransform === 'scaleX(-1)') {
|
||||
document.getElementById('largeVideo').style.webkitTransform
|
||||
= "none";
|
||||
}
|
||||
|
||||
// Change the way we'll be measuring and positioning large video
|
||||
|
||||
getVideoSize = isDesktop
|
||||
getVideoSize = largeVideoState.isDesktop
|
||||
? getDesktopVideoSize
|
||||
: getCameraVideoSize;
|
||||
getVideoPosition = isDesktop
|
||||
getVideoPosition = largeVideoState.isDesktop
|
||||
? getDesktopVideoPosition
|
||||
: getCameraVideoPosition;
|
||||
|
||||
if (isVisible) {
|
||||
if (largeVideoState.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);
|
||||
if (largeVideoState.oldJid) {
|
||||
var oldResourceJid = Strophe.getResourceFromJid(largeVideoState.oldJid);
|
||||
VideoLayout.enableDominantSpeaker(oldResourceJid, false);
|
||||
}
|
||||
|
||||
// Enable new dominant speaker in the remote videos section.
|
||||
var userJid = getJidFromVideoSrc(newSrc);
|
||||
if (userJid)
|
||||
{
|
||||
var resourceJid = Strophe.getResourceFromJid(userJid);
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue