Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

videolayout.js 51KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439
  1. var VideoLayout = (function (my) {
  2. var currentDominantSpeaker = null;
  3. var lastNCount = config.channelLastN;
  4. var lastNEndpointsCache = [];
  5. var largeVideoState = {
  6. updateInProgress: false,
  7. newSrc: ''
  8. };
  9. my.changeLocalAudio = function(stream) {
  10. connection.jingle.localAudio = stream;
  11. RTC.attachMediaStream($('#localAudio'), stream);
  12. document.getElementById('localAudio').autoplay = true;
  13. document.getElementById('localAudio').volume = 0;
  14. };
  15. my.changeLocalVideo = function(stream, flipX) {
  16. connection.jingle.localVideo = stream;
  17. var localVideo = document.createElement('video');
  18. localVideo.id = 'localVideo_' + stream.id;
  19. localVideo.autoplay = true;
  20. localVideo.volume = 0; // is it required if audio is separated ?
  21. localVideo.oncontextmenu = function () { return false; };
  22. var localVideoContainer = document.getElementById('localVideoWrapper');
  23. localVideoContainer.appendChild(localVideo);
  24. // Set default display name.
  25. setDisplayName('localVideoContainer');
  26. AudioLevels.updateAudioLevelCanvas();
  27. var localVideoSelector = $('#' + localVideo.id);
  28. // Add click handler to both video and video wrapper elements in case
  29. // there's no video.
  30. localVideoSelector.click(function () {
  31. VideoLayout.handleVideoThumbClicked(localVideo.src);
  32. });
  33. $('#localVideoContainer').click(function () {
  34. VideoLayout.handleVideoThumbClicked(localVideo.src);
  35. });
  36. // Add hover handler
  37. $('#localVideoContainer').hover(
  38. function() {
  39. VideoLayout.showDisplayName('localVideoContainer', true);
  40. },
  41. function() {
  42. if (!VideoLayout.isLargeVideoVisible()
  43. || localVideo.src !== $('#largeVideo').attr('src'))
  44. VideoLayout.showDisplayName('localVideoContainer', false);
  45. }
  46. );
  47. // Add stream ended handler
  48. stream.onended = function () {
  49. localVideoContainer.removeChild(localVideo);
  50. VideoLayout.updateRemovedVideo(localVideo.src);
  51. };
  52. // Flip video x axis if needed
  53. flipXLocalVideo = flipX;
  54. if (flipX) {
  55. localVideoSelector.addClass("flipVideoX");
  56. }
  57. // Attach WebRTC stream
  58. var simulcast = new Simulcast();
  59. var videoStream = simulcast.getLocalVideoStream();
  60. RTC.attachMediaStream(localVideoSelector, videoStream);
  61. localVideoSrc = localVideo.src;
  62. VideoLayout.updateLargeVideo(localVideoSrc, 0);
  63. };
  64. /**
  65. * Checks if removed video is currently displayed and tries to display
  66. * another one instead.
  67. * @param removedVideoSrc src stream identifier of the video.
  68. */
  69. my.updateRemovedVideo = function(removedVideoSrc) {
  70. if (removedVideoSrc === $('#largeVideo').attr('src')) {
  71. // this is currently displayed as large
  72. // pick the last visible video in the row
  73. // if nobody else is left, this picks the local video
  74. var pick
  75. = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video')
  76. .get(0);
  77. if (!pick) {
  78. console.info("Last visible video no longer exists");
  79. pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
  80. if (!pick || !pick.src) {
  81. // Try local video
  82. console.info("Fallback to local video...");
  83. pick = $('#remoteVideos>span>span>video').get(0);
  84. }
  85. }
  86. // mute if localvideo
  87. if (pick) {
  88. VideoLayout.updateLargeVideo(pick.src, pick.volume);
  89. } else {
  90. console.warn("Failed to elect large video");
  91. }
  92. }
  93. };
  94. /**
  95. * Updates the large video with the given new video source.
  96. */
  97. my.updateLargeVideo = function(newSrc, vol) {
  98. console.log('hover in', newSrc);
  99. if ($('#largeVideo').attr('src') != newSrc) {
  100. // Due to the simulcast the localVideoSrc may have changed when the
  101. // fadeOut event triggers. In that case the getJidFromVideoSrc and
  102. // isVideoSrcDesktop methods will not function correctly.
  103. //
  104. // Also, again due to the simulcast, the updateLargeVideo method can
  105. // be called multiple times almost simultaneously. Therefore, we
  106. // store the state here and update only once.
  107. largeVideoState.newSrc = newSrc;
  108. largeVideoState.isVisible = $('#largeVideo').is(':visible');
  109. largeVideoState.isDesktop = isVideoSrcDesktop(newSrc);
  110. largeVideoState.userJid = getJidFromVideoSrc(newSrc);
  111. // Screen stream is already rotated
  112. largeVideoState.flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
  113. var oldSrc = $('#largeVideo').attr('src');
  114. largeVideoState.oldJid = getJidFromVideoSrc(oldSrc);
  115. var fade = false;
  116. if (largeVideoState.oldJid != largeVideoState.userJid) {
  117. fade = true;
  118. // we want the notification to trigger even if userJid is undefined,
  119. // or null.
  120. $(document).trigger("selectedendpointchanged", [largeVideoState.userJid]);
  121. }
  122. if (!largeVideoState.updateInProgress) {
  123. largeVideoState.updateInProgress = true;
  124. var doUpdate = function () {
  125. $('#largeVideo').attr('src', largeVideoState.newSrc);
  126. var videoTransform = document.getElementById('largeVideo')
  127. .style.webkitTransform;
  128. if (largeVideoState.flipX && videoTransform !== 'scaleX(-1)') {
  129. document.getElementById('largeVideo').style.webkitTransform
  130. = "scaleX(-1)";
  131. }
  132. else if (!largeVideoState.flipX && videoTransform === 'scaleX(-1)') {
  133. document.getElementById('largeVideo').style.webkitTransform
  134. = "none";
  135. }
  136. // Change the way we'll be measuring and positioning large video
  137. getVideoSize = largeVideoState.isDesktop
  138. ? getDesktopVideoSize
  139. : getCameraVideoSize;
  140. getVideoPosition = largeVideoState.isDesktop
  141. ? getDesktopVideoPosition
  142. : getCameraVideoPosition;
  143. if (largeVideoState.isVisible) {
  144. // Only if the large video is currently visible.
  145. // Disable previous dominant speaker video.
  146. if (largeVideoState.oldJid) {
  147. var oldResourceJid = Strophe.getResourceFromJid(largeVideoState.oldJid);
  148. VideoLayout.enableDominantSpeaker(oldResourceJid, false);
  149. }
  150. // Enable new dominant speaker in the remote videos section.
  151. if (largeVideoState.userJid) {
  152. var resourceJid = Strophe.getResourceFromJid(largeVideoState.userJid);
  153. VideoLayout.enableDominantSpeaker(resourceJid, true);
  154. }
  155. largeVideoState.updateInProgress = false;
  156. if (fade) {
  157. // using "this" should be ok because we're called
  158. // from within the fadeOut event.
  159. $(this).fadeIn(300);
  160. }
  161. }
  162. };
  163. if (fade) {
  164. $('#largeVideo').fadeOut(300, doUpdate);
  165. } else {
  166. doUpdate();
  167. }
  168. }
  169. }
  170. };
  171. my.handleVideoThumbClicked = function(videoSrc) {
  172. // Restore style for previously focused video
  173. var focusJid = getJidFromVideoSrc(focusedVideoSrc);
  174. var oldContainer = getParticipantContainer(focusJid);
  175. if (oldContainer) {
  176. oldContainer.removeClass("videoContainerFocused");
  177. }
  178. // Unlock current focused.
  179. if (focusedVideoSrc === videoSrc)
  180. {
  181. focusedVideoSrc = null;
  182. var dominantSpeakerVideo = null;
  183. // Enable the currently set dominant speaker.
  184. if (currentDominantSpeaker) {
  185. dominantSpeakerVideo
  186. = $('#participant_' + currentDominantSpeaker + '>video')
  187. .get(0);
  188. if (dominantSpeakerVideo) {
  189. VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1);
  190. }
  191. }
  192. return;
  193. }
  194. // Lock new video
  195. focusedVideoSrc = videoSrc;
  196. // Update focused/pinned interface.
  197. var userJid = getJidFromVideoSrc(videoSrc);
  198. if (userJid)
  199. {
  200. var container = getParticipantContainer(userJid);
  201. container.addClass("videoContainerFocused");
  202. }
  203. // Triggers a "video.selected" event. The "false" parameter indicates
  204. // this isn't a prezi.
  205. $(document).trigger("video.selected", [false]);
  206. VideoLayout.updateLargeVideo(videoSrc, 1);
  207. $('audio').each(function (idx, el) {
  208. if (el.id.indexOf('mixedmslabel') !== -1) {
  209. el.volume = 0;
  210. el.volume = 1;
  211. }
  212. });
  213. };
  214. /**
  215. * Positions the large video.
  216. *
  217. * @param videoWidth the stream video width
  218. * @param videoHeight the stream video height
  219. */
  220. my.positionLarge = function (videoWidth, videoHeight) {
  221. var videoSpaceWidth = $('#videospace').width();
  222. var videoSpaceHeight = window.innerHeight;
  223. var videoSize = getVideoSize(videoWidth,
  224. videoHeight,
  225. videoSpaceWidth,
  226. videoSpaceHeight);
  227. var largeVideoWidth = videoSize[0];
  228. var largeVideoHeight = videoSize[1];
  229. var videoPosition = getVideoPosition(largeVideoWidth,
  230. largeVideoHeight,
  231. videoSpaceWidth,
  232. videoSpaceHeight);
  233. var horizontalIndent = videoPosition[0];
  234. var verticalIndent = videoPosition[1];
  235. positionVideo($('#largeVideo'),
  236. largeVideoWidth,
  237. largeVideoHeight,
  238. horizontalIndent, verticalIndent);
  239. };
  240. /**
  241. * Shows/hides the large video.
  242. */
  243. my.setLargeVideoVisible = function(isVisible) {
  244. var largeVideoJid = getJidFromVideoSrc($('#largeVideo').attr('src'));
  245. var resourceJid = Strophe.getResourceFromJid(largeVideoJid);
  246. if (isVisible) {
  247. $('#largeVideo').css({visibility: 'visible'});
  248. $('.watermark').css({visibility: 'visible'});
  249. VideoLayout.enableDominantSpeaker(resourceJid, true);
  250. }
  251. else {
  252. $('#largeVideo').css({visibility: 'hidden'});
  253. $('.watermark').css({visibility: 'hidden'});
  254. VideoLayout.enableDominantSpeaker(resourceJid, false);
  255. }
  256. };
  257. /**
  258. * Indicates if the large video is currently visible.
  259. *
  260. * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
  261. */
  262. my.isLargeVideoVisible = function() {
  263. return $('#largeVideo').is(':visible');
  264. };
  265. /**
  266. * Checks if container for participant identified by given peerJid exists
  267. * in the document and creates it eventually.
  268. *
  269. * @param peerJid peer Jid to check.
  270. *
  271. * @return Returns <tt>true</tt> if the peer container exists,
  272. * <tt>false</tt> - otherwise
  273. */
  274. my.ensurePeerContainerExists = function(peerJid) {
  275. ContactList.ensureAddContact(peerJid);
  276. var resourceJid = Strophe.getResourceFromJid(peerJid);
  277. var videoSpanId = 'participant_' + resourceJid;
  278. if ($('#' + videoSpanId).length > 0) {
  279. // If there's been a focus change, make sure we add focus related
  280. // interface!!
  281. if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0)
  282. addRemoteVideoMenu( peerJid,
  283. document.getElementById(videoSpanId));
  284. }
  285. else {
  286. var container
  287. = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
  288. // Set default display name.
  289. setDisplayName(videoSpanId);
  290. var nickfield = document.createElement('span');
  291. nickfield.className = "nick";
  292. nickfield.appendChild(document.createTextNode(resourceJid));
  293. container.appendChild(nickfield);
  294. // In case this is not currently in the last n we don't show it.
  295. if (lastNCount
  296. && lastNCount > 0
  297. && $('#remoteVideos>span').length >= lastNCount + 2) {
  298. showPeerContainer(resourceJid, false);
  299. }
  300. else
  301. VideoLayout.resizeThumbnails();
  302. }
  303. };
  304. my.addRemoteVideoContainer = function(peerJid, spanId) {
  305. var container = document.createElement('span');
  306. container.id = spanId;
  307. container.className = 'videocontainer';
  308. var remotes = document.getElementById('remoteVideos');
  309. // If the peerJid is null then this video span couldn't be directly
  310. // associated with a participant (this could happen in the case of prezi).
  311. if (focus && peerJid != null)
  312. addRemoteVideoMenu(peerJid, container);
  313. remotes.appendChild(container);
  314. AudioLevels.updateAudioLevelCanvas(peerJid);
  315. return container;
  316. };
  317. /**
  318. * Creates an audio or video stream element.
  319. */
  320. my.createStreamElement = function (sid, stream) {
  321. var isVideo = stream.getVideoTracks().length > 0;
  322. var element = isVideo
  323. ? document.createElement('video')
  324. : document.createElement('audio');
  325. var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_')
  326. + sid + '_' + stream.id;
  327. element.id = id;
  328. element.autoplay = true;
  329. element.oncontextmenu = function () { return false; };
  330. return element;
  331. };
  332. my.addRemoteStreamElement
  333. = function (container, sid, stream, peerJid, thessrc) {
  334. var newElementId = null;
  335. var isVideo = stream.getVideoTracks().length > 0;
  336. if (container) {
  337. var streamElement = VideoLayout.createStreamElement(sid, stream);
  338. newElementId = streamElement.id;
  339. container.appendChild(streamElement);
  340. var sel = $('#' + newElementId);
  341. sel.hide();
  342. // If the container is currently visible we attach the stream.
  343. if (!isVideo
  344. || (container.offsetParent !== null && isVideo)) {
  345. var simulcast = new Simulcast();
  346. var videoStream = simulcast.getReceivingVideoStream(stream);
  347. RTC.attachMediaStream(sel, videoStream);
  348. if (isVideo)
  349. waitForRemoteVideo(sel, thessrc, stream);
  350. }
  351. stream.onended = function () {
  352. console.log('stream ended', this);
  353. VideoLayout.removeRemoteStreamElement(
  354. stream, isVideo, container);
  355. if (peerJid)
  356. ContactList.removeContact(peerJid);
  357. };
  358. // Add click handler.
  359. container.onclick = function (event) {
  360. /*
  361. * FIXME It turns out that videoThumb may not exist (if there is
  362. * no actual video).
  363. */
  364. var videoThumb = $('#' + container.id + '>video').get(0);
  365. if (videoThumb)
  366. VideoLayout.handleVideoThumbClicked(videoThumb.src);
  367. event.preventDefault();
  368. return false;
  369. };
  370. // Add hover handler
  371. $(container).hover(
  372. function() {
  373. VideoLayout.showDisplayName(container.id, true);
  374. },
  375. function() {
  376. var videoSrc = null;
  377. if ($('#' + container.id + '>video')
  378. && $('#' + container.id + '>video').length > 0) {
  379. videoSrc = $('#' + container.id + '>video').get(0).src;
  380. }
  381. // If the video has been "pinned" by the user we want to
  382. // keep the display name on place.
  383. if (!VideoLayout.isLargeVideoVisible()
  384. || videoSrc !== $('#largeVideo').attr('src'))
  385. VideoLayout.showDisplayName(container.id, false);
  386. }
  387. );
  388. }
  389. return newElementId;
  390. };
  391. /**
  392. * Removes the remote stream element corresponding to the given stream and
  393. * parent container.
  394. *
  395. * @param stream the stream
  396. * @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
  397. * @param container
  398. */
  399. my.removeRemoteStreamElement = function (stream, isVideo, container) {
  400. if (!container)
  401. return;
  402. var select = null;
  403. var removedVideoSrc = null;
  404. if (isVideo) {
  405. select = $('#' + container.id + '>video');
  406. removedVideoSrc = select.get(0).src;
  407. }
  408. else
  409. select = $('#' + container.id + '>audio');
  410. // Remove video source from the mapping.
  411. delete videoSrcToSsrc[removedVideoSrc];
  412. // Mark video as removed to cancel waiting loop(if video is removed
  413. // before has started)
  414. select.removed = true;
  415. select.remove();
  416. var audioCount = $('#' + container.id + '>audio').length;
  417. var videoCount = $('#' + container.id + '>video').length;
  418. if (!audioCount && !videoCount) {
  419. console.log("Remove whole user", container.id);
  420. // Remove whole container
  421. container.remove();
  422. Util.playSoundNotification('userLeft');
  423. VideoLayout.resizeThumbnails();
  424. }
  425. if (removedVideoSrc)
  426. VideoLayout.updateRemovedVideo(removedVideoSrc);
  427. };
  428. /**
  429. * Show/hide peer container for the given resourceJid.
  430. */
  431. function showPeerContainer(resourceJid, isShow) {
  432. var peerContainer = $('#participant_' + resourceJid);
  433. if (!peerContainer)
  434. return;
  435. if (!peerContainer.is(':visible') && isShow)
  436. peerContainer.show();
  437. else if (peerContainer.is(':visible') && !isShow)
  438. peerContainer.hide();
  439. VideoLayout.resizeThumbnails();
  440. ContactList.setClickable(resourceJid, isShow);
  441. };
  442. /**
  443. * Sets the display name for the given video span id.
  444. */
  445. function setDisplayName(videoSpanId, displayName) {
  446. var nameSpan = $('#' + videoSpanId + '>span.displayname');
  447. var defaultLocalDisplayName = "Me";
  448. // If we already have a display name for this video.
  449. if (nameSpan.length > 0) {
  450. var nameSpanElement = nameSpan.get(0);
  451. if (nameSpanElement.id === 'localDisplayName' &&
  452. $('#localDisplayName').text() !== displayName) {
  453. if (displayName && displayName.length > 0)
  454. $('#localDisplayName').text(displayName + ' (me)');
  455. else
  456. $('#localDisplayName').text(defaultLocalDisplayName);
  457. } else {
  458. if (displayName && displayName.length > 0)
  459. $('#' + videoSpanId + '_name').text(displayName);
  460. else
  461. $('#' + videoSpanId + '_name').text(interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
  462. }
  463. } else {
  464. var editButton = null;
  465. nameSpan = document.createElement('span');
  466. nameSpan.className = 'displayname';
  467. $('#' + videoSpanId)[0].appendChild(nameSpan);
  468. if (videoSpanId === 'localVideoContainer') {
  469. editButton = createEditDisplayNameButton();
  470. nameSpan.innerText = defaultLocalDisplayName;
  471. }
  472. else {
  473. nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
  474. }
  475. if (displayName && displayName.length > 0) {
  476. nameSpan.innerText = displayName;
  477. }
  478. if (!editButton) {
  479. nameSpan.id = videoSpanId + '_name';
  480. } else {
  481. nameSpan.id = 'localDisplayName';
  482. $('#' + videoSpanId)[0].appendChild(editButton);
  483. var editableText = document.createElement('input');
  484. editableText.className = 'displayname';
  485. editableText.type = 'text';
  486. editableText.id = 'editDisplayName';
  487. if (displayName && displayName.length) {
  488. editableText.value
  489. = displayName.substring(0, displayName.indexOf(' (me)'));
  490. }
  491. editableText.setAttribute('style', 'display:none;');
  492. editableText.setAttribute('placeholder', 'ex. Jane Pink');
  493. $('#' + videoSpanId)[0].appendChild(editableText);
  494. $('#localVideoContainer .displayname')
  495. .bind("click", function (e) {
  496. e.preventDefault();
  497. $('#localDisplayName').hide();
  498. $('#editDisplayName').show();
  499. $('#editDisplayName').focus();
  500. $('#editDisplayName').select();
  501. var inputDisplayNameHandler = function (name) {
  502. if (nickname !== name) {
  503. nickname = name;
  504. window.localStorage.displayname = nickname;
  505. connection.emuc.addDisplayNameToPresence(nickname);
  506. connection.emuc.sendPresence();
  507. Chat.setChatConversationMode(true);
  508. }
  509. if (!$('#localDisplayName').is(":visible")) {
  510. if (nickname)
  511. $('#localDisplayName').text(nickname + " (me)");
  512. else
  513. $('#localDisplayName')
  514. .text(defaultLocalDisplayName);
  515. $('#localDisplayName').show();
  516. }
  517. $('#editDisplayName').hide();
  518. };
  519. $('#editDisplayName').one("focusout", function (e) {
  520. inputDisplayNameHandler(this.value);
  521. });
  522. $('#editDisplayName').on('keydown', function (e) {
  523. if (e.keyCode === 13) {
  524. e.preventDefault();
  525. inputDisplayNameHandler(this.value);
  526. }
  527. });
  528. });
  529. }
  530. }
  531. };
  532. /**
  533. * Shows/hides the display name on the remote video.
  534. * @param videoSpanId the identifier of the video span element
  535. * @param isShow indicates if the display name should be shown or hidden
  536. */
  537. my.showDisplayName = function(videoSpanId, isShow) {
  538. var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
  539. if (isShow) {
  540. if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
  541. nameSpan.setAttribute("style", "display:inline-block;");
  542. }
  543. else {
  544. if (nameSpan)
  545. nameSpan.setAttribute("style", "display:none;");
  546. }
  547. };
  548. /**
  549. * Shows the presence status message for the given video.
  550. */
  551. my.setPresenceStatus = function (videoSpanId, statusMsg) {
  552. if (!$('#' + videoSpanId).length) {
  553. // No container
  554. return;
  555. }
  556. var statusSpan = $('#' + videoSpanId + '>span.status');
  557. if (!statusSpan.length) {
  558. //Add status span
  559. statusSpan = document.createElement('span');
  560. statusSpan.className = 'status';
  561. statusSpan.id = videoSpanId + '_status';
  562. $('#' + videoSpanId)[0].appendChild(statusSpan);
  563. statusSpan = $('#' + videoSpanId + '>span.status');
  564. }
  565. // Display status
  566. if (statusMsg && statusMsg.length) {
  567. $('#' + videoSpanId + '_status').text(statusMsg);
  568. statusSpan.get(0).setAttribute("style", "display:inline-block;");
  569. }
  570. else {
  571. // Hide
  572. statusSpan.get(0).setAttribute("style", "display:none;");
  573. }
  574. };
  575. /**
  576. * Shows a visual indicator for the focus of the conference.
  577. * Currently if we're not the owner of the conference we obtain the focus
  578. * from the connection.jingle.sessions.
  579. */
  580. my.showFocusIndicator = function() {
  581. if (focus !== null) {
  582. var indicatorSpan = $('#localVideoContainer .focusindicator');
  583. if (indicatorSpan.children().length === 0)
  584. {
  585. createFocusIndicatorElement(indicatorSpan[0]);
  586. }
  587. }
  588. else if (Object.keys(connection.jingle.sessions).length > 0) {
  589. // If we're only a participant the focus will be the only session we have.
  590. var session
  591. = connection.jingle.sessions
  592. [Object.keys(connection.jingle.sessions)[0]];
  593. var focusId
  594. = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
  595. var focusContainer = document.getElementById(focusId);
  596. if (!focusContainer) {
  597. console.error("No focus container!");
  598. return;
  599. }
  600. var indicatorSpan = $('#' + focusId + ' .focusindicator');
  601. if (!indicatorSpan || indicatorSpan.length === 0) {
  602. indicatorSpan = document.createElement('span');
  603. indicatorSpan.className = 'focusindicator';
  604. focusContainer.appendChild(indicatorSpan);
  605. createFocusIndicatorElement(indicatorSpan);
  606. }
  607. }
  608. };
  609. /**
  610. * Shows video muted indicator over small videos.
  611. */
  612. my.showVideoIndicator = function(videoSpanId, isMuted) {
  613. var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
  614. if (isMuted === 'false') {
  615. if (videoMutedSpan.length > 0) {
  616. videoMutedSpan.remove();
  617. }
  618. }
  619. else {
  620. if(videoMutedSpan.length == 0) {
  621. videoMutedSpan = document.createElement('span');
  622. videoMutedSpan.className = 'videoMuted';
  623. $('#' + videoSpanId)[0].appendChild(videoMutedSpan);
  624. var mutedIndicator = document.createElement('i');
  625. mutedIndicator.className = 'icon-camera-disabled';
  626. Util.setTooltip(mutedIndicator,
  627. "Participant has<br/>stopped the camera.",
  628. "top");
  629. videoMutedSpan.appendChild(mutedIndicator);
  630. }
  631. var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
  632. videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
  633. videoMutedSpan.css({right: ((audioMutedSpan.length > 0)?'30px':'0px')});
  634. }
  635. };
  636. /**
  637. * Shows audio muted indicator over small videos.
  638. * @param {string} isMuted
  639. */
  640. my.showAudioIndicator = function(videoSpanId, isMuted) {
  641. var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
  642. if (isMuted === 'false') {
  643. if (audioMutedSpan.length > 0) {
  644. audioMutedSpan.popover('hide');
  645. audioMutedSpan.remove();
  646. }
  647. }
  648. else {
  649. if(audioMutedSpan.length > 0 )
  650. return;
  651. audioMutedSpan = document.createElement('span');
  652. audioMutedSpan.className = 'audioMuted';
  653. Util.setTooltip(audioMutedSpan,
  654. "Participant is muted",
  655. "top");
  656. $('#' + videoSpanId)[0].appendChild(audioMutedSpan);
  657. var mutedIndicator = document.createElement('i');
  658. mutedIndicator.className = 'icon-mic-disabled';
  659. audioMutedSpan.appendChild(mutedIndicator);
  660. }
  661. };
  662. /*
  663. * Shows or hides the audio muted indicator over the local thumbnail video.
  664. * @param {boolean} isMuted
  665. */
  666. my.showLocalAudioIndicator = function(isMuted) {
  667. VideoLayout.showAudioIndicator('localVideoContainer', isMuted.toString());
  668. };
  669. /**
  670. * Resizes the large video container.
  671. */
  672. my.resizeLargeVideoContainer = function () {
  673. Chat.resizeChat();
  674. var availableHeight = window.innerHeight;
  675. var availableWidth = Util.getAvailableVideoWidth();
  676. if (availableWidth < 0 || availableHeight < 0) return;
  677. $('#videospace').width(availableWidth);
  678. $('#videospace').height(availableHeight);
  679. $('#largeVideoContainer').width(availableWidth);
  680. $('#largeVideoContainer').height(availableHeight);
  681. VideoLayout.resizeThumbnails();
  682. };
  683. /**
  684. * Resizes thumbnails.
  685. */
  686. my.resizeThumbnails = function() {
  687. var videoSpaceWidth = $('#remoteVideos').width();
  688. var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
  689. var width = thumbnailSize[0];
  690. var height = thumbnailSize[1];
  691. // size videos so that while keeping AR and max height, we have a
  692. // nice fit
  693. $('#remoteVideos').height(height);
  694. $('#remoteVideos>span').width(width);
  695. $('#remoteVideos>span').height(height);
  696. $(document).trigger("remotevideo.resized", [width, height]);
  697. };
  698. /**
  699. * Enables the dominant speaker UI.
  700. *
  701. * @param resourceJid the jid indicating the video element to
  702. * activate/deactivate
  703. * @param isEnable indicates if the dominant speaker should be enabled or
  704. * disabled
  705. */
  706. my.enableDominantSpeaker = function(resourceJid, isEnable) {
  707. var videoSpanId = null;
  708. var videoContainerId = null;
  709. if (resourceJid
  710. === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
  711. videoSpanId = 'localVideoWrapper';
  712. videoContainerId = 'localVideoContainer';
  713. }
  714. else {
  715. videoSpanId = 'participant_' + resourceJid;
  716. videoContainerId = videoSpanId;
  717. }
  718. var displayName = resourceJid;
  719. var nameSpan = $('#' + videoContainerId + '>span.displayname');
  720. if (nameSpan.length > 0)
  721. displayName = nameSpan.text();
  722. console.log("UI enable dominant speaker",
  723. displayName,
  724. resourceJid,
  725. isEnable);
  726. videoSpan = document.getElementById(videoContainerId);
  727. if (!videoSpan) {
  728. console.error("No video element for jid", resourceJid);
  729. return;
  730. }
  731. var video = $('#' + videoSpanId + '>video');
  732. if (video && video.length > 0) {
  733. if (isEnable) {
  734. VideoLayout.showDisplayName(videoContainerId, true);
  735. if (!videoSpan.classList.contains("dominantspeaker"))
  736. videoSpan.classList.add("dominantspeaker");
  737. video.css({visibility: 'hidden'});
  738. }
  739. else {
  740. VideoLayout.showDisplayName(videoContainerId, false);
  741. if (videoSpan.classList.contains("dominantspeaker"))
  742. videoSpan.classList.remove("dominantspeaker");
  743. video.css({visibility: 'visible'});
  744. }
  745. }
  746. };
  747. /**
  748. * Gets the selector of video thumbnail container for the user identified by
  749. * given <tt>userJid</tt>
  750. * @param userJid user's Jid for whom we want to get the video container.
  751. */
  752. function getParticipantContainer(userJid)
  753. {
  754. if (!userJid)
  755. return null;
  756. if (userJid === connection.emuc.myroomjid)
  757. return $("#localVideoContainer");
  758. else
  759. return $("#participant_" + Strophe.getResourceFromJid(userJid));
  760. }
  761. /**
  762. * Sets the size and position of the given video element.
  763. *
  764. * @param video the video element to position
  765. * @param width the desired video width
  766. * @param height the desired video height
  767. * @param horizontalIndent the left and right indent
  768. * @param verticalIndent the top and bottom indent
  769. */
  770. function positionVideo(video,
  771. width,
  772. height,
  773. horizontalIndent,
  774. verticalIndent) {
  775. video.width(width);
  776. video.height(height);
  777. video.css({ top: verticalIndent + 'px',
  778. bottom: verticalIndent + 'px',
  779. left: horizontalIndent + 'px',
  780. right: horizontalIndent + 'px'});
  781. }
  782. /**
  783. * Calculates the thumbnail size.
  784. *
  785. * @param videoSpaceWidth the width of the video space
  786. */
  787. my.calculateThumbnailSize = function (videoSpaceWidth) {
  788. // Calculate the available height, which is the inner window height minus
  789. // 39px for the header minus 2px for the delimiter lines on the top and
  790. // bottom of the large video, minus the 36px space inside the remoteVideos
  791. // container used for highlighting shadow.
  792. var availableHeight = 100;
  793. var numvids = $('#remoteVideos>span:visible').length;
  794. if (lastNCount && lastNCount > 0) {
  795. numvids = Math.min(lastNCount + 1, numvids);
  796. }
  797. // Remove the 3px borders arround videos and border around the remote
  798. // videos area and the 4 pixels between the local video and the others
  799. //TODO: Find out where the 4 pixels come from and remove them
  800. var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70 - 4;
  801. var availableWidth = availableWinWidth / numvids;
  802. var aspectRatio = 16.0 / 9.0;
  803. var maxHeight = Math.min(160, availableHeight);
  804. availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
  805. if (availableHeight < availableWidth / aspectRatio) {
  806. availableWidth = Math.floor(availableHeight * aspectRatio);
  807. }
  808. return [availableWidth, availableHeight];
  809. };
  810. /**
  811. * Returns an array of the video dimensions, so that it keeps it's aspect
  812. * ratio and fits available area with it's larger dimension. This method
  813. * ensures that whole video will be visible and can leave empty areas.
  814. *
  815. * @return an array with 2 elements, the video width and the video height
  816. */
  817. function getDesktopVideoSize(videoWidth,
  818. videoHeight,
  819. videoSpaceWidth,
  820. videoSpaceHeight) {
  821. if (!videoWidth)
  822. videoWidth = currentVideoWidth;
  823. if (!videoHeight)
  824. videoHeight = currentVideoHeight;
  825. var aspectRatio = videoWidth / videoHeight;
  826. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  827. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  828. videoSpaceHeight -= $('#remoteVideos').outerHeight();
  829. if (availableWidth / aspectRatio >= videoSpaceHeight)
  830. {
  831. availableHeight = videoSpaceHeight;
  832. availableWidth = availableHeight * aspectRatio;
  833. }
  834. if (availableHeight * aspectRatio >= videoSpaceWidth)
  835. {
  836. availableWidth = videoSpaceWidth;
  837. availableHeight = availableWidth / aspectRatio;
  838. }
  839. return [availableWidth, availableHeight];
  840. }
  841. /**
  842. * Creates the edit display name button.
  843. *
  844. * @returns the edit button
  845. */
  846. function createEditDisplayNameButton() {
  847. var editButton = document.createElement('a');
  848. editButton.className = 'displayname';
  849. Util.setTooltip(editButton,
  850. 'Click to edit your<br/>display name',
  851. "top");
  852. editButton.innerHTML = '<i class="fa fa-pencil"></i>';
  853. return editButton;
  854. }
  855. /**
  856. * Creates the element indicating the focus of the conference.
  857. *
  858. * @param parentElement the parent element where the focus indicator will
  859. * be added
  860. */
  861. function createFocusIndicatorElement(parentElement) {
  862. var focusIndicator = document.createElement('i');
  863. focusIndicator.className = 'fa fa-star';
  864. parentElement.appendChild(focusIndicator);
  865. Util.setTooltip(parentElement,
  866. "The owner of<br/>this conference",
  867. "top");
  868. }
  869. /**
  870. * Updates the remote video menu.
  871. *
  872. * @param jid the jid indicating the video for which we're adding a menu.
  873. * @param isMuted indicates the current mute state
  874. */
  875. my.updateRemoteVideoMenu = function(jid, isMuted) {
  876. var muteMenuItem
  877. = $('#remote_popupmenu_'
  878. + Strophe.getResourceFromJid(jid)
  879. + '>li>a.mutelink');
  880. var mutedIndicator = "<i class='icon-mic-disabled'></i>";
  881. if (muteMenuItem.length) {
  882. var muteLink = muteMenuItem.get(0);
  883. if (isMuted === 'true') {
  884. muteLink.innerHTML = mutedIndicator + ' Muted';
  885. muteLink.className = 'mutelink disabled';
  886. }
  887. else {
  888. muteLink.innerHTML = mutedIndicator + ' Mute';
  889. muteLink.className = 'mutelink';
  890. }
  891. }
  892. };
  893. /**
  894. * Returns the current dominant speaker resource jid.
  895. */
  896. my.getDominantSpeakerResourceJid = function () {
  897. return currentDominantSpeaker;
  898. };
  899. /**
  900. * Returns the corresponding resource jid to the given peer container
  901. * DOM element.
  902. *
  903. * @return the corresponding resource jid to the given peer container
  904. * DOM element
  905. */
  906. my.getPeerContainerResourceJid = function (containerElement) {
  907. var i = containerElement.id.indexOf('participant_');
  908. if (i >= 0)
  909. return containerElement.id.substring(i + 12);
  910. };
  911. /**
  912. * Adds the remote video menu element for the given <tt>jid</tt> in the
  913. * given <tt>parentElement</tt>.
  914. *
  915. * @param jid the jid indicating the video for which we're adding a menu.
  916. * @param parentElement the parent element where this menu will be added
  917. */
  918. function addRemoteVideoMenu(jid, parentElement) {
  919. var spanElement = document.createElement('span');
  920. spanElement.className = 'remotevideomenu';
  921. parentElement.appendChild(spanElement);
  922. var menuElement = document.createElement('i');
  923. menuElement.className = 'fa fa-angle-down';
  924. menuElement.title = 'Remote user controls';
  925. spanElement.appendChild(menuElement);
  926. // <ul class="popupmenu">
  927. // <li><a href="#">Mute</a></li>
  928. // <li><a href="#">Eject</a></li>
  929. // </ul>
  930. var popupmenuElement = document.createElement('ul');
  931. popupmenuElement.className = 'popupmenu';
  932. popupmenuElement.id
  933. = 'remote_popupmenu_' + Strophe.getResourceFromJid(jid);
  934. spanElement.appendChild(popupmenuElement);
  935. var muteMenuItem = document.createElement('li');
  936. var muteLinkItem = document.createElement('a');
  937. var mutedIndicator = "<i class='icon-mic-disabled'></i>";
  938. if (!mutedAudios[jid]) {
  939. muteLinkItem.innerHTML = mutedIndicator + 'Mute';
  940. muteLinkItem.className = 'mutelink';
  941. }
  942. else {
  943. muteLinkItem.innerHTML = mutedIndicator + ' Muted';
  944. muteLinkItem.className = 'mutelink disabled';
  945. }
  946. muteLinkItem.onclick = function(){
  947. if ($(this).attr('disabled') != undefined) {
  948. event.preventDefault();
  949. }
  950. var isMute = !mutedAudios[jid];
  951. connection.moderate.setMute(jid, isMute);
  952. popupmenuElement.setAttribute('style', 'display:none;');
  953. if (isMute) {
  954. this.innerHTML = mutedIndicator + ' Muted';
  955. this.className = 'mutelink disabled';
  956. }
  957. else {
  958. this.innerHTML = mutedIndicator + ' Mute';
  959. this.className = 'mutelink';
  960. }
  961. };
  962. muteMenuItem.appendChild(muteLinkItem);
  963. popupmenuElement.appendChild(muteMenuItem);
  964. var ejectIndicator = "<i class='fa fa-eject'></i>";
  965. var ejectMenuItem = document.createElement('li');
  966. var ejectLinkItem = document.createElement('a');
  967. ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
  968. ejectLinkItem.onclick = function(){
  969. connection.moderate.eject(jid);
  970. popupmenuElement.setAttribute('style', 'display:none;');
  971. };
  972. ejectMenuItem.appendChild(ejectLinkItem);
  973. popupmenuElement.appendChild(ejectMenuItem);
  974. var paddingSpan = document.createElement('span');
  975. paddingSpan.className = 'popupmenuPadding';
  976. popupmenuElement.appendChild(paddingSpan);
  977. }
  978. /**
  979. * On audio muted event.
  980. */
  981. $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
  982. if (jid === connection.emuc.myroomjid) {
  983. // The local mute indicator is controlled locally
  984. return;
  985. }
  986. VideoLayout.ensurePeerContainerExists(jid);
  987. if (focus) {
  988. mutedAudios[jid] = isMuted;
  989. VideoLayout.updateRemoteVideoMenu(jid, isMuted);
  990. }
  991. var videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
  992. if (videoSpanId)
  993. VideoLayout.showAudioIndicator(videoSpanId, isMuted);
  994. });
  995. /**
  996. * On video muted event.
  997. */
  998. $(document).bind('videomuted.muc', function (event, jid, isMuted) {
  999. var videoSpanId = null;
  1000. if (jid === connection.emuc.myroomjid) {
  1001. videoSpanId = 'localVideoContainer';
  1002. } else {
  1003. VideoLayout.ensurePeerContainerExists(jid);
  1004. videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
  1005. }
  1006. if (videoSpanId)
  1007. VideoLayout.showVideoIndicator(videoSpanId, isMuted);
  1008. });
  1009. /**
  1010. * Display name changed.
  1011. */
  1012. $(document).bind('displaynamechanged',
  1013. function (event, jid, displayName, status) {
  1014. if (jid === 'localVideoContainer'
  1015. || jid === connection.emuc.myroomjid) {
  1016. setDisplayName('localVideoContainer',
  1017. displayName);
  1018. } else {
  1019. VideoLayout.ensurePeerContainerExists(jid);
  1020. setDisplayName(
  1021. 'participant_' + Strophe.getResourceFromJid(jid),
  1022. displayName,
  1023. status);
  1024. }
  1025. });
  1026. /**
  1027. * On dominant speaker changed event.
  1028. */
  1029. $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
  1030. // We ignore local user events.
  1031. if (resourceJid
  1032. === Strophe.getResourceFromJid(connection.emuc.myroomjid))
  1033. return;
  1034. // Update the current dominant speaker.
  1035. if (resourceJid !== currentDominantSpeaker) {
  1036. var oldSpeakerVideoSpanId = "participant_" + currentDominantSpeaker,
  1037. newSpeakerVideoSpanId = "participant_" + resourceJid;
  1038. if($("#" + oldSpeakerVideoSpanId + ">span.displayname").text() ===
  1039. interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME) {
  1040. setDisplayName(oldSpeakerVideoSpanId, null);
  1041. }
  1042. if($("#" + newSpeakerVideoSpanId + ">span.displayname").text() ===
  1043. interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME) {
  1044. setDisplayName(newSpeakerVideoSpanId,
  1045. interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
  1046. }
  1047. currentDominantSpeaker = resourceJid;
  1048. } else {
  1049. return;
  1050. }
  1051. // Obtain container for new dominant speaker.
  1052. var container = document.getElementById(
  1053. 'participant_' + resourceJid);
  1054. // Local video will not have container found, but that's ok
  1055. // since we don't want to switch to local video.
  1056. if (container && !focusedVideoSrc)
  1057. {
  1058. var video = container.getElementsByTagName("video");
  1059. // Update the large video if the video source is already available,
  1060. // otherwise wait for the "videoactive.jingle" event.
  1061. if (video.length && video[0].currentTime > 0)
  1062. VideoLayout.updateLargeVideo(video[0].src);
  1063. }
  1064. });
  1065. /**
  1066. * On last N change event.
  1067. *
  1068. * @param event the event that notified us
  1069. * @param lastNEndpoints the list of last N endpoints
  1070. * @param endpointsEnteringLastN the list currently entering last N
  1071. * endpoints
  1072. */
  1073. $(document).bind('lastnchanged', function ( event,
  1074. lastNEndpoints,
  1075. endpointsEnteringLastN,
  1076. stream) {
  1077. if (lastNCount !== lastNEndpoints.length)
  1078. lastNCount = lastNEndpoints.length;
  1079. lastNEndpointsCache = lastNEndpoints;
  1080. $('#remoteVideos>span').each(function( index, element ) {
  1081. var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
  1082. if (resourceJid
  1083. && lastNEndpoints.indexOf(resourceJid) < 0) {
  1084. console.log("Remove from last N", resourceJid);
  1085. showPeerContainer(resourceJid, false);
  1086. }
  1087. });
  1088. if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
  1089. endpointsEnteringLastN = lastNEndpoints;
  1090. if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
  1091. endpointsEnteringLastN.forEach(function (resourceJid) {
  1092. if (!$('#participant_' + resourceJid).is(':visible')) {
  1093. console.log("Add to last N", resourceJid);
  1094. showPeerContainer(resourceJid, true);
  1095. mediaStreams.some(function (mediaStream) {
  1096. if (mediaStream.peerjid
  1097. && Strophe.getResourceFromJid(mediaStream.peerjid)
  1098. === resourceJid
  1099. && mediaStream.type === mediaStream.VIDEO_TYPE) {
  1100. var sel = $('#participant_' + resourceJid + '>video');
  1101. var simulcast = new Simulcast();
  1102. var videoStream = simulcast.getReceivingVideoStream(mediaStream.stream);
  1103. RTC.attachMediaStream(sel, videoStream);
  1104. waitForRemoteVideo(
  1105. sel,
  1106. mediaStream.ssrc,
  1107. mediaStream.stream);
  1108. return true;
  1109. }
  1110. });
  1111. }
  1112. });
  1113. }
  1114. });
  1115. $(document).bind('videoactive.jingle', function (event, videoelem) {
  1116. if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
  1117. // ignore mixedmslabela0 and v0
  1118. videoelem.show();
  1119. VideoLayout.resizeThumbnails();
  1120. var videoParent = videoelem.parent();
  1121. var parentResourceJid = null;
  1122. if (videoParent)
  1123. parentResourceJid
  1124. = VideoLayout.getPeerContainerResourceJid(videoParent[0]);
  1125. // Update the large video to the last added video only if there's no
  1126. // current dominant or focused speaker or update it to the current
  1127. // dominant speaker.
  1128. if ((!focusedVideoSrc && !VideoLayout.getDominantSpeakerResourceJid())
  1129. || (parentResourceJid
  1130. && VideoLayout.getDominantSpeakerResourceJid()
  1131. === parentResourceJid)) {
  1132. VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
  1133. }
  1134. VideoLayout.showFocusIndicator();
  1135. }
  1136. });
  1137. /**
  1138. * On simulcast layers changed event.
  1139. */
  1140. $(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
  1141. var simulcast = new Simulcast();
  1142. endpointSimulcastLayers.forEach(function (esl) {
  1143. var primarySSRC = esl.simulcastLayer.primarySSRC;
  1144. var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
  1145. // Get session and stream from msid.
  1146. var session, electedStream;
  1147. var i, j, k;
  1148. if (connection.jingle) {
  1149. var keys = Object.keys(connection.jingle.sessions);
  1150. for (i = 0; i < keys.length; i++) {
  1151. var sid = keys[i];
  1152. if (electedStream) {
  1153. // stream found, stop.
  1154. break;
  1155. }
  1156. session = connection.jingle.sessions[sid];
  1157. if (session.remoteStreams) {
  1158. for (j = 0; j < session.remoteStreams.length; j++) {
  1159. var remoteStream = session.remoteStreams[j];
  1160. if (electedStream) {
  1161. // stream found, stop.
  1162. break;
  1163. }
  1164. var tracks = remoteStream.getVideoTracks();
  1165. if (tracks) {
  1166. for (k = 0; k < tracks.length; k++) {
  1167. var track = tracks[k];
  1168. if (msid === [remoteStream.id, track.id].join(' ')) {
  1169. electedStream = new webkitMediaStream([track]);
  1170. // stream found, stop.
  1171. break;
  1172. }
  1173. }
  1174. }
  1175. }
  1176. }
  1177. }
  1178. }
  1179. if (session && electedStream) {
  1180. console.info('Switching simulcast substream.');
  1181. console.info([esl, primarySSRC, msid, session, electedStream]);
  1182. var msidParts = msid.split(' ');
  1183. var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join(''));
  1184. var updateLargeVideo = (ssrc2jid[videoSrcToSsrc[selRemoteVideo.attr('src')]]
  1185. == ssrc2jid[videoSrcToSsrc[largeVideoState.newSrc]]);
  1186. var updateFocusedVideoSrc = (selRemoteVideo.attr('src') == focusedVideoSrc);
  1187. var electedStreamUrl = webkitURL.createObjectURL(electedStream);
  1188. selRemoteVideo.attr('src', electedStreamUrl);
  1189. videoSrcToSsrc[selRemoteVideo.attr('src')] = primarySSRC;
  1190. if (updateLargeVideo) {
  1191. VideoLayout.updateLargeVideo(electedStreamUrl);
  1192. }
  1193. if (updateFocusedVideoSrc) {
  1194. focusedVideoSrc = electedStreamUrl;
  1195. }
  1196. } else {
  1197. console.error('Could not find a stream or a session.', session, electedStream);
  1198. }
  1199. });
  1200. });
  1201. return my;
  1202. }(VideoLayout || {}));