/* global $, APP, Strophe, interfaceConfig */ /* jshint -W101 */ var Avatar = require("../avatar/Avatar"); var RTCBrowserType = require("../../RTC/RTCBrowserType"); var UIUtil = require("../util/UIUtil"); var UIEvents = require("../../../service/UI/UIEvents"); var xmpp = require("../../xmpp/xmpp"); var ToolbarToggler = require("../toolbars/ToolbarToggler"); // FIXME: With Temasys we have to re-select everytime //var video = $('#largeVideo'); var currentVideoWidth = null; var currentVideoHeight = null; // By default we use camera var getVideoSize = getCameraVideoSize; var getVideoPosition = getCameraVideoPosition; /** * The small video instance that is displayed in the large video * @type {SmallVideo} */ var currentSmallVideo = null; /** * Indicates whether the large video is enabled. * @type {boolean} */ var isEnabled = true; /** * Current large video state. * Possible values - video, prezi or etherpad. * @type {string} */ var state = "video"; /** * Returns the html element associated with the passed state of large video * @param state the state. * @returns {JQuery|*|jQuery|HTMLElement} the container. */ function getContainerByState(state) { var selector = null; switch (state) { case "video": selector = "#largeVideoWrapper"; break; case "etherpad": selector = "#etherpad>iframe"; break; case "prezi": selector = "#presentation>iframe"; break; } return (selector !== null)? $(selector) : null; } /** * Sets the size and position of the given video element. * * @param video the video element to position * @param width the desired video width * @param height the desired video height * @param horizontalIndent the left and right indent * @param verticalIndent the top and bottom indent */ function positionVideo(video, width, height, horizontalIndent, verticalIndent, animate) { if (animate) { video.animate({ width: width, height: height, top: verticalIndent, bottom: verticalIndent, left: horizontalIndent, right: horizontalIndent }, { queue: false, duration: 500 }); } else { video.width(width); video.height(height); video.css({ top: verticalIndent + 'px', bottom: verticalIndent + 'px', left: horizontalIndent + 'px', right: horizontalIndent + 'px'}); } } /** * Returns an array of the video dimensions, so that it keeps it's aspect * ratio and fits available area with it's larger dimension. This method * ensures that whole video will be visible and can leave empty areas. * * @return an array with 2 elements, the video width and the video height */ function getDesktopVideoSize(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { if (!videoWidth) videoWidth = currentVideoWidth; if (!videoHeight) videoHeight = currentVideoHeight; var aspectRatio = videoWidth / videoHeight; var availableWidth = Math.max(videoWidth, videoSpaceWidth); var availableHeight = Math.max(videoHeight, videoSpaceHeight); videoSpaceHeight -= $('#remoteVideos').outerHeight(); if (availableWidth / aspectRatio >= videoSpaceHeight) { availableHeight = videoSpaceHeight; availableWidth = availableHeight * aspectRatio; } if (availableHeight * aspectRatio >= videoSpaceWidth) { availableWidth = videoSpaceWidth; availableHeight = availableWidth / aspectRatio; } return [availableWidth, availableHeight]; } /** * Returns an array of the video horizontal and vertical indents, * so that if fits its parent. * * @return an array with 2 elements, the horizontal indent and the vertical * indent */ function getCameraVideoPosition(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { // Parent height isn't completely calculated when we position the video in // full screen mode and this is why we use the screen height in this case. // Need to think it further at some point and implement it properly. var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; if (isFullScreen) videoSpaceHeight = window.innerHeight; var horizontalIndent = (videoSpaceWidth - videoWidth) / 2; var verticalIndent = (videoSpaceHeight - videoHeight) / 2; return [horizontalIndent, verticalIndent]; } /** * Returns an array of the video horizontal and vertical indents. * Centers horizontally and top aligns vertically. * * @return an array with 2 elements, the horizontal indent and the vertical * indent */ function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { var horizontalIndent = (videoSpaceWidth - videoWidth) / 2; var verticalIndent = 0;// Top aligned return [horizontalIndent, verticalIndent]; } /** * Returns an array of the video dimensions. It respects the * VIDEO_LAYOUT_FIT config, to fit the video to the screen, by hiding some parts * of it, or to fit it to the height or width. * * @param videoWidth the original video width * @param videoHeight the original video height * @param videoSpaceWidth the width of the video space * @param videoSpaceHeight the height of the video space * @return an array with 2 elements, the video width and the video height */ function getCameraVideoSize(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { if (!videoWidth) videoWidth = currentVideoWidth; if (!videoHeight) videoHeight = currentVideoHeight; var aspectRatio = videoWidth / videoHeight; var availableWidth = videoWidth; var availableHeight = videoHeight; if (interfaceConfig.VIDEO_LAYOUT_FIT == 'height') { availableHeight = videoSpaceHeight; availableWidth = availableHeight*aspectRatio; } else if (interfaceConfig.VIDEO_LAYOUT_FIT == 'width') { availableWidth = videoSpaceWidth; availableHeight = availableWidth/aspectRatio; } else if (interfaceConfig.VIDEO_LAYOUT_FIT == 'both') { availableWidth = Math.max(videoWidth, videoSpaceWidth); availableHeight = Math.max(videoHeight, videoSpaceHeight); if (availableWidth / aspectRatio < videoSpaceHeight) { availableHeight = videoSpaceHeight; availableWidth = availableHeight * aspectRatio; } if (availableHeight * aspectRatio < videoSpaceWidth) { availableWidth = videoSpaceWidth; availableHeight = availableWidth / aspectRatio; } } return [availableWidth, availableHeight]; } /** * Updates the src of the active speaker avatar * @param jid of the current active speaker */ function updateActiveSpeakerAvatarSrc() { var avatar = $("#activeSpeakerAvatar")[0]; var jid = currentSmallVideo.peerJid; var url = Avatar.getActiveSpeakerUrl(jid); if (avatar.src === url) return; if (jid) { avatar.src = url; currentSmallVideo.showAvatar(); } } /** * Change the video source of the large video. * @param isVisible */ function changeVideo(isVisible) { if (!currentSmallVideo) { console.error("Unable to change large video - no 'currentSmallVideo'"); return; } updateActiveSpeakerAvatarSrc(); var largeVideoElement = $('#largeVideo')[0]; APP.RTC.setVideoSrc(largeVideoElement, currentSmallVideo.getSrc()); var flipX = currentSmallVideo.flipX; largeVideoElement.style.transform = flipX ? "scaleX(-1)" : "none"; var isDesktop = currentSmallVideo.getVideoType() === 'screen'; // Change the way we'll be measuring and positioning large video getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; getVideoPosition = isDesktop ? getDesktopVideoPosition : getCameraVideoPosition; // Only if the large video is currently visible. if (isVisible) { LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo); $('#largeVideoWrapper').fadeTo(300, 1); } } /** * Creates the html elements for the large video. */ function createLargeVideoHTML() { var html = '
'; html += '
' + '
' + '
' + '
' + '' + ' jitsi.org' + ''+ '
' + '' + '' + '
' + '
' + '' + '
' + ''; html += '
'; $(html).prependTo("#videospace"); if (interfaceConfig.SHOW_JITSI_WATERMARK) { var leftWatermarkDiv = $("#largeVideoContainer div[class='watermark leftwatermark']"); leftWatermarkDiv.css({display: 'block'}); leftWatermarkDiv.parent().get(0).href = interfaceConfig.JITSI_WATERMARK_LINK; } if (interfaceConfig.SHOW_BRAND_WATERMARK) { var rightWatermarkDiv = $("#largeVideoContainer div[class='watermark rightwatermark']"); rightWatermarkDiv.css({display: 'block'}); rightWatermarkDiv.parent().get(0).href = interfaceConfig.BRAND_WATERMARK_LINK; rightWatermarkDiv.get(0).style.backgroundImage = "url(images/rightwatermark.png)"; } if (interfaceConfig.SHOW_POWERED_BY) { $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'}); } if (!RTCBrowserType.isIExplorer()) { $('#largeVideo').volume = 0; } } var LargeVideo = { init: function (VideoLayout, emitter) { if(!isEnabled) return; createLargeVideoHTML(); this.VideoLayout = VideoLayout; this.eventEmitter = emitter; this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT); var self = this; // Listen for large video size updates var largeVideo = $('#largeVideo')[0]; var onplaying = function (arg1, arg2, arg3) { // re-select if (RTCBrowserType.isTemasysPluginUsed()) largeVideo = $('#largeVideo')[0]; currentVideoWidth = largeVideo.videoWidth; currentVideoHeight = largeVideo.videoHeight; self.position(currentVideoWidth, currentVideoHeight); }; largeVideo.onplaying = onplaying; }, /** * Indicates if the large video is currently visible. * * @return true if visible, false - otherwise */ isLargeVideoVisible: function() { return $('#largeVideoWrapper').is(':visible'); }, /** * Returns true if the user is currently displayed on large video. */ isCurrentlyOnLarge: function (resourceJid) { return currentSmallVideo && resourceJid && currentSmallVideo.getResourceJid() === resourceJid; }, /** * Updates the large video with the given new video source. */ updateLargeVideo: function (resourceJid, forceUpdate) { if(!isEnabled) return; var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid); console.info('hover in ' + resourceJid + ', video: ', newSmallVideo); if (!newSmallVideo) { console.error("Small video not found for: " + resourceJid); return; } if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) { $('#activeSpeaker').css('visibility', 'hidden'); var oldSmallVideo = null; if (currentSmallVideo) { oldSmallVideo = currentSmallVideo; } currentSmallVideo = newSmallVideo; var oldJid = null; if (oldSmallVideo) oldJid = oldSmallVideo.peerJid; if (oldJid !== resourceJid) { // we want the notification to trigger even if userJid is undefined, // or null. this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid); } // We are doing fadeOut/fadeIn animations on parent div which wraps // largeVideo, because when Temasys plugin is in use it replaces //