diff --git a/audio_levels.js b/audio_levels.js new file mode 100644 index 0000000000..3f97343785 --- /dev/null +++ b/audio_levels.js @@ -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 || {}); \ No newline at end of file diff --git a/canvas_util.js b/canvas_util.js new file mode 100644 index 0000000000..bc96f030ab --- /dev/null +++ b/canvas_util.js @@ -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 || {}); \ No newline at end of file