Audio level indication. Improvements in rollover and active speaker UI.
This commit is contained in:
parent
304cdf5b40
commit
50f1521d5c
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* The audio Levels plugin.
|
||||
*/
|
||||
var AudioLevels = (function(my) {
|
||||
var CANVAS_EXTRA = 104;
|
||||
var CANVAS_RADIUS = 7;
|
||||
var SHADOW_COLOR = '#00ccff';
|
||||
var audioLevelCanvasCache = {};
|
||||
|
||||
/**
|
||||
* Updates the audio level canvas for the given peerJid. If the canvas
|
||||
* didn't exist we create it.
|
||||
*/
|
||||
my.updateAudioLevelCanvas = function (peerJid) {
|
||||
var resourceJid = null;
|
||||
var videoSpanId = null;
|
||||
if (!peerJid)
|
||||
videoSpanId = 'localVideoContainer';
|
||||
else {
|
||||
resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
videoSpanId = 'participant_' + resourceJid;
|
||||
}
|
||||
|
||||
videoSpan = document.getElementById(videoSpanId);
|
||||
|
||||
if (!videoSpan) {
|
||||
if (resourceJid)
|
||||
console.error("No video element for jid", resourceJid);
|
||||
else
|
||||
console.error("No video element for local video.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
|
||||
|
||||
var videoSpaceWidth = $('#remoteVideos').width();
|
||||
var thumbnailSize
|
||||
= VideoLayout.calculateThumbnailSize(videoSpaceWidth);
|
||||
var thumbnailWidth = thumbnailSize[0];
|
||||
var thumbnailHeight = thumbnailSize[1];
|
||||
|
||||
if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
|
||||
|
||||
audioLevelCanvas = document.createElement('canvas');
|
||||
audioLevelCanvas.className = "audiolevel";
|
||||
audioLevelCanvas.style.bottom = "-" + CANVAS_EXTRA/2 + "px";
|
||||
audioLevelCanvas.style.left = "-" + CANVAS_EXTRA/2 + "px";
|
||||
resizeAudioLevelCanvas( audioLevelCanvas,
|
||||
thumbnailWidth,
|
||||
thumbnailHeight);
|
||||
|
||||
videoSpan.appendChild(audioLevelCanvas);
|
||||
} else {
|
||||
audioLevelCanvas = audioLevelCanvas.get(0);
|
||||
|
||||
resizeAudioLevelCanvas( audioLevelCanvas,
|
||||
thumbnailWidth,
|
||||
thumbnailHeight);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the audio level UI for the given resourceJid.
|
||||
*
|
||||
* @param resourceJid the resource jid indicating the video element for
|
||||
* which we draw the audio level
|
||||
* @param audioLevel the newAudio level to render
|
||||
*/
|
||||
my.updateAudioLevel = function (resourceJid, audioLevel) {
|
||||
drawAudioLevelCanvas(resourceJid, audioLevel);
|
||||
|
||||
var videoSpanId = null;
|
||||
if (resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
||||
videoSpanId = 'localVideoContainer';
|
||||
else
|
||||
videoSpanId = 'participant_' + resourceJid;
|
||||
|
||||
var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
|
||||
|
||||
if (!audioLevelCanvas)
|
||||
return ;
|
||||
|
||||
var drawContext = audioLevelCanvas.getContext('2d');
|
||||
|
||||
var canvasCache = audioLevelCanvasCache[resourceJid];
|
||||
|
||||
drawContext.clearRect (0, 0,
|
||||
audioLevelCanvas.width, audioLevelCanvas.height);
|
||||
drawContext.drawImage(canvasCache, 0, 0);
|
||||
};
|
||||
|
||||
function resizeAudioLevelCanvas(audioLevelCanvas,
|
||||
thumbnailWidth,
|
||||
thumbnailHeight) {
|
||||
audioLevelCanvas.width = thumbnailWidth + CANVAS_EXTRA;
|
||||
audioLevelCanvas.height = thumbnailHeight + CANVAS_EXTRA;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the audio level canvas into the cached canvas object.
|
||||
*
|
||||
* @param resourceJid the resource jid indicating the video element for
|
||||
* which we draw the audio level
|
||||
* @param audioLevel the newAudio level to render
|
||||
*/
|
||||
function drawAudioLevelCanvas(resourceJid, audioLevel) {
|
||||
if (!audioLevelCanvasCache[resourceJid]) {
|
||||
var videoSpanId = null;
|
||||
if (resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
||||
videoSpanId = 'localVideoContainer';
|
||||
else
|
||||
videoSpanId = 'participant_' + resourceJid;
|
||||
|
||||
var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
|
||||
|
||||
audioLevelCanvasCache[resourceJid]
|
||||
= CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
|
||||
}
|
||||
|
||||
var canvas = audioLevelCanvasCache[resourceJid];
|
||||
|
||||
var drawContext = canvas.getContext('2d');
|
||||
|
||||
drawContext.clearRect (0, 0, canvas.width, canvas.height);
|
||||
|
||||
var shadowLevel = getShadowLevel(audioLevel);
|
||||
|
||||
if (shadowLevel > 0)
|
||||
// drawContext, x, y, w, h, r, shadowColor, shadowLevel
|
||||
CanvasUtil.drawRoundRectGlow( drawContext,
|
||||
CANVAS_EXTRA/2, CANVAS_EXTRA/2,
|
||||
canvas.width - CANVAS_EXTRA,
|
||||
canvas.height - CANVAS_EXTRA,
|
||||
CANVAS_RADIUS,
|
||||
SHADOW_COLOR,
|
||||
shadowLevel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the shadow/glow level for the given audio level.
|
||||
*
|
||||
* @param audioLevel the audio level from which we determine the shadow
|
||||
* level
|
||||
*/
|
||||
function getShadowLevel (audioLevel) {
|
||||
var shadowLevel = 0;
|
||||
|
||||
if (audioLevel <= 0.3) {
|
||||
shadowLevel = Math.round(CANVAS_EXTRA/2*(audioLevel/0.3));
|
||||
}
|
||||
else if (audioLevel <= 0.6) {
|
||||
shadowLevel = Math.round(CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
|
||||
}
|
||||
else {
|
||||
shadowLevel = Math.round(CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
|
||||
}
|
||||
return shadowLevel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates that the remote video has been resized.
|
||||
*/
|
||||
$(document).bind('remotevideo.resized', function (event, width, height) {
|
||||
var resized = false;
|
||||
$('#remoteVideos>span>canvas').each(function() {
|
||||
var canvas = $(this).get(0);
|
||||
if (canvas.width !== width + CANVAS_EXTRA) {
|
||||
canvas.width = width + CANVAS_EXTRA;
|
||||
resized = true;
|
||||
}
|
||||
|
||||
if (canvas.heigh !== height + CANVAS_EXTRA) {
|
||||
canvas.height = height + CANVAS_EXTRA;
|
||||
resized = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (resized)
|
||||
Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
|
||||
audioLevelCanvasCache[resourceJid].width
|
||||
= width + CANVAS_EXTRA;
|
||||
audioLevelCanvasCache[resourceJid].height
|
||||
= height + CANVAS_EXTRA;
|
||||
});
|
||||
});
|
||||
|
||||
return my;
|
||||
|
||||
})(AudioLevels || {});
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Utility class for drawing canvas shapes.
|
||||
*/
|
||||
var CanvasUtil = (function(my) {
|
||||
|
||||
/**
|
||||
* Draws a round rectangle with a glow. The glowWidth indicates the depth
|
||||
* of the glow.
|
||||
*
|
||||
* @param drawContext the context of the canvas to draw to
|
||||
* @param x the x coordinate of the round rectangle
|
||||
* @param y the y coordinate of the round rectangle
|
||||
* @param w the width of the round rectangle
|
||||
* @param h the height of the round rectangle
|
||||
* @param glowColor the color of the glow
|
||||
* @param glowWidth the width of the glow
|
||||
*/
|
||||
my.drawRoundRectGlow
|
||||
= function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
|
||||
|
||||
// Save the previous state of the context.
|
||||
drawContext.save();
|
||||
|
||||
if (w < 2 * r) r = w / 2;
|
||||
if (h < 2 * r) r = h / 2;
|
||||
|
||||
// Draw a round rectangle.
|
||||
drawContext.beginPath();
|
||||
drawContext.moveTo(x+r, y);
|
||||
drawContext.arcTo(x+w, y, x+w, y+h, r);
|
||||
drawContext.arcTo(x+w, y+h, x, y+h, r);
|
||||
drawContext.arcTo(x, y+h, x, y, r);
|
||||
drawContext.arcTo(x, y, x+w, y, r);
|
||||
drawContext.closePath();
|
||||
|
||||
// Add a shadow around the rectangle
|
||||
drawContext.shadowColor = glowColor;
|
||||
drawContext.shadowBlur = glowWidth;
|
||||
drawContext.shadowOffsetX = 0;
|
||||
drawContext.shadowOffsetY = 0;
|
||||
|
||||
// Fill the shape.
|
||||
drawContext.fill();
|
||||
|
||||
drawContext.save();
|
||||
|
||||
drawContext.restore();
|
||||
|
||||
// 1) Uncomment this line to use Composite Operation, which is doing the
|
||||
// same as the clip function below and is also antialiasing the round
|
||||
// border, but is said to be less fast performance wise.
|
||||
|
||||
// drawContext.globalCompositeOperation='destination-out';
|
||||
|
||||
drawContext.beginPath();
|
||||
drawContext.moveTo(x+r, y);
|
||||
drawContext.arcTo(x+w, y, x+w, y+h, r);
|
||||
drawContext.arcTo(x+w, y+h, x, y+h, r);
|
||||
drawContext.arcTo(x, y+h, x, y, r);
|
||||
drawContext.arcTo(x, y, x+w, y, r);
|
||||
drawContext.closePath();
|
||||
|
||||
// 2) Uncomment this line to use Composite Operation, which is doing the
|
||||
// same as the clip function below and is also antialiasing the round
|
||||
// border, but is said to be less fast performance wise.
|
||||
|
||||
// drawContext.fill();
|
||||
|
||||
// Comment these two lines if choosing to do the same with composite
|
||||
// operation above 1 and 2.
|
||||
drawContext.clip();
|
||||
drawContext.clearRect(0, 0, 277, 200);
|
||||
|
||||
// Restore the previous context state.
|
||||
drawContext.restore();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones the given canvas.
|
||||
*
|
||||
* @return the new cloned canvas.
|
||||
*/
|
||||
my.cloneCanvas = function (oldCanvas) {
|
||||
|
||||
//create a new canvas
|
||||
var newCanvas = document.createElement('canvas');
|
||||
var context = newCanvas.getContext('2d');
|
||||
|
||||
//set dimensions
|
||||
newCanvas.width = oldCanvas.width;
|
||||
newCanvas.height = oldCanvas.height;
|
||||
|
||||
//apply the old canvas to the new one
|
||||
context.drawImage(oldCanvas, 0, 0);
|
||||
|
||||
//return the new canvas
|
||||
return newCanvas;
|
||||
};
|
||||
|
||||
return my;
|
||||
})(CanvasUtil || {});
|
Loading…
Reference in New Issue