/* global $, APP, interfaceConfig */ /* jshint -W101 */ import Avatar from "../avatar/Avatar"; import ToolbarToggler from "../toolbars/ToolbarToggler"; import UIUtil from "../util/UIUtil"; import UIEvents from "../../../service/UI/UIEvents"; var RTCBrowserType = require("../../RTC/RTCBrowserType"); // 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; default: return null; } return $(selector); } /** * 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, bottom: verticalIndent, left: horizontalIndent, right: horizontalIndent }); } } /** * 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); var filmstrip = $("#remoteVideos"); if (!filmstrip.hasClass("hidden")) videoSpaceHeight -= filmstrip.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 */ function updateActiveSpeakerAvatarSrc() { let avatar = $("#activeSpeakerAvatar"); let id = currentSmallVideo.id; let url = Avatar.getActiveSpeakerUrl(id); if (id && avatar.attr('src') !== url) { avatar.attr('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(); let largeVideoElement = $('#largeVideo'); currentSmallVideo.stream.attach(largeVideoElement); let flipX = currentSmallVideo.flipX; largeVideoElement.css({ transform: flipX ? "scaleX(-1)" : "none" }); LargeVideo.updateVideoSizeAndPosition(currentSmallVideo.getVideoType()); // 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 (id) { return id && id === this.getId(); }, /** * Updates the large video with the given new video source. */ updateLargeVideo: function (id, forceUpdate) { if(!isEnabled) { return; } let newSmallVideo = this.VideoLayout.getSmallVideo(id); console.info(`hover in ${id} , video: `, newSmallVideo); if (!newSmallVideo) { console.error("Small video not found for: " + id); return; } if (!LargeVideo.isCurrentlyOnLarge(id) || forceUpdate) { $('#activeSpeaker').css('visibility', 'hidden'); let oldId = this.getId(); currentSmallVideo = newSmallVideo; if (oldId !== id) { // we want the notification to trigger even if id is undefined, // or null. this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id); } // We are doing fadeOut/fadeIn animations on parent div which wraps // largeVideo, because when Temasys plugin is in use it replaces //