You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AudioLevels.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. * Copyright @ 2015 Atlassian Pty Ltd
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. var CanvasUtil = require("./CanvasUtils");
  17. var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
  18. function initActiveSpeakerAudioLevels() {
  19. var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
  20. var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
  21. // Draw a circle.
  22. ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
  23. // Add a shadow around the circle
  24. ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
  25. ASDrawContext.shadowOffsetX = 0;
  26. ASDrawContext.shadowOffsetY = 0;
  27. }
  28. /**
  29. * The audio Levels plugin.
  30. */
  31. var AudioLevels = (function(my) {
  32. var audioLevelCanvasCache = {};
  33. my.LOCAL_LEVEL = 'local';
  34. my.init = function () {
  35. initActiveSpeakerAudioLevels();
  36. }
  37. /**
  38. * Updates the audio level canvas for the given peerJid. If the canvas
  39. * didn't exist we create it.
  40. */
  41. my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
  42. var resourceJid = null;
  43. var videoSpanId = null;
  44. if (!peerJid)
  45. videoSpanId = 'localVideoContainer';
  46. else {
  47. resourceJid = Strophe.getResourceFromJid(peerJid);
  48. videoSpanId = 'participant_' + resourceJid;
  49. }
  50. var videoSpan = document.getElementById(videoSpanId);
  51. if (!videoSpan) {
  52. if (resourceJid)
  53. console.error("No video element for jid", resourceJid);
  54. else
  55. console.error("No video element for local video.");
  56. return;
  57. }
  58. var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
  59. var videoSpaceWidth = $('#remoteVideos').width();
  60. var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
  61. var thumbnailWidth = thumbnailSize[0];
  62. var thumbnailHeight = thumbnailSize[1];
  63. if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
  64. audioLevelCanvas = document.createElement('canvas');
  65. audioLevelCanvas.className = "audiolevel";
  66. audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
  67. audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
  68. resizeAudioLevelCanvas( audioLevelCanvas,
  69. thumbnailWidth,
  70. thumbnailHeight);
  71. videoSpan.appendChild(audioLevelCanvas);
  72. } else {
  73. audioLevelCanvas = audioLevelCanvas.get(0);
  74. resizeAudioLevelCanvas( audioLevelCanvas,
  75. thumbnailWidth,
  76. thumbnailHeight);
  77. }
  78. };
  79. /**
  80. * Updates the audio level UI for the given resourceJid.
  81. *
  82. * @param resourceJid the resource jid indicating the video element for
  83. * which we draw the audio level
  84. * @param audioLevel the newAudio level to render
  85. */
  86. my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
  87. drawAudioLevelCanvas(resourceJid, audioLevel);
  88. var videoSpanId = getVideoSpanId(resourceJid);
  89. var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
  90. if (!audioLevelCanvas)
  91. return;
  92. var drawContext = audioLevelCanvas.getContext('2d');
  93. var canvasCache = audioLevelCanvasCache[resourceJid];
  94. drawContext.clearRect (0, 0,
  95. audioLevelCanvas.width, audioLevelCanvas.height);
  96. drawContext.drawImage(canvasCache, 0, 0);
  97. if(resourceJid === AudioLevels.LOCAL_LEVEL) {
  98. if(!APP.xmpp.myJid()) {
  99. return;
  100. }
  101. resourceJid = APP.xmpp.myResource();
  102. }
  103. if(resourceJid === largeVideoResourceJid) {
  104. window.requestAnimationFrame(function () {
  105. AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
  106. });
  107. }
  108. };
  109. my.updateActiveSpeakerAudioLevel = function(audioLevel) {
  110. if($("#activeSpeaker").css("visibility") == "hidden")
  111. return;
  112. ASDrawContext.clearRect(0, 0, 300, 300);
  113. if(audioLevel == 0)
  114. return;
  115. ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
  116. // Fill the shape.
  117. ASDrawContext.fill();
  118. };
  119. /**
  120. * Resizes the given audio level canvas to match the given thumbnail size.
  121. */
  122. function resizeAudioLevelCanvas(audioLevelCanvas,
  123. thumbnailWidth,
  124. thumbnailHeight) {
  125. audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
  126. audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
  127. }
  128. /**
  129. * Draws the audio level canvas into the cached canvas object.
  130. *
  131. * @param resourceJid the resource jid indicating the video element for
  132. * which we draw the audio level
  133. * @param audioLevel the newAudio level to render
  134. */
  135. function drawAudioLevelCanvas(resourceJid, audioLevel) {
  136. if (!audioLevelCanvasCache[resourceJid]) {
  137. var videoSpanId = getVideoSpanId(resourceJid);
  138. var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
  139. /*
  140. * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
  141. * In such a case, the method CanvasUtil.cloneCanvas may throw an
  142. * error. Since audio levels are frequently updated, the errors have
  143. * been observed to pile into the console, strain the CPU.
  144. */
  145. if (audioLevelCanvasOrig)
  146. {
  147. audioLevelCanvasCache[resourceJid]
  148. = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
  149. }
  150. }
  151. var canvas = audioLevelCanvasCache[resourceJid];
  152. if (!canvas)
  153. return;
  154. var drawContext = canvas.getContext('2d');
  155. drawContext.clearRect(0, 0, canvas.width, canvas.height);
  156. var shadowLevel = getShadowLevel(audioLevel);
  157. if (shadowLevel > 0)
  158. // drawContext, x, y, w, h, r, shadowColor, shadowLevel
  159. CanvasUtil.drawRoundRectGlow( drawContext,
  160. interfaceConfig.CANVAS_EXTRA/2, interfaceConfig.CANVAS_EXTRA/2,
  161. canvas.width - interfaceConfig.CANVAS_EXTRA,
  162. canvas.height - interfaceConfig.CANVAS_EXTRA,
  163. interfaceConfig.CANVAS_RADIUS,
  164. interfaceConfig.SHADOW_COLOR,
  165. shadowLevel);
  166. }
  167. /**
  168. * Returns the shadow/glow level for the given audio level.
  169. *
  170. * @param audioLevel the audio level from which we determine the shadow
  171. * level
  172. */
  173. function getShadowLevel (audioLevel) {
  174. var shadowLevel = 0;
  175. if (audioLevel <= 0.3) {
  176. shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
  177. }
  178. else if (audioLevel <= 0.6) {
  179. shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
  180. }
  181. else {
  182. shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
  183. }
  184. return shadowLevel;
  185. }
  186. /**
  187. * Returns the video span id corresponding to the given resourceJid or local
  188. * user.
  189. */
  190. function getVideoSpanId(resourceJid) {
  191. var videoSpanId = null;
  192. if (resourceJid === AudioLevels.LOCAL_LEVEL
  193. || (APP.xmpp.myResource() && resourceJid
  194. === APP.xmpp.myResource()))
  195. videoSpanId = 'localVideoContainer';
  196. else
  197. videoSpanId = 'participant_' + resourceJid;
  198. return videoSpanId;
  199. }
  200. /**
  201. * Indicates that the remote video has been resized.
  202. */
  203. $(document).bind('remotevideo.resized', function (event, width, height) {
  204. var resized = false;
  205. $('#remoteVideos>span>canvas').each(function() {
  206. var canvas = $(this).get(0);
  207. if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
  208. canvas.width = width + interfaceConfig.CANVAS_EXTRA;
  209. resized = true;
  210. }
  211. if (canvas.heigh !== height + interfaceConfig.CANVAS_EXTRA) {
  212. canvas.height = height + interfaceConfig.CANVAS_EXTRA;
  213. resized = true;
  214. }
  215. });
  216. if (resized)
  217. Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
  218. audioLevelCanvasCache[resourceJid].width
  219. = width + interfaceConfig.CANVAS_EXTRA;
  220. audioLevelCanvasCache[resourceJid].height
  221. = height + interfaceConfig.CANVAS_EXTRA;
  222. });
  223. });
  224. return my;
  225. })(AudioLevels || {});
  226. module.exports = AudioLevels;