/* global $, config, interfaceConfig, APP, JitsiMeetJS */ import ConnectionIndicator from "./ConnectionIndicator"; import UIUtil from "../util/UIUtil"; import UIEvents from "../../../service/UI/UIEvents"; import SmallVideo from "./SmallVideo"; const RTCUIUtils = JitsiMeetJS.util.RTCUIHelper; const TrackEvents = JitsiMeetJS.events.track; function LocalVideo(VideoLayout, emitter) { this.videoSpanId = "localVideoContainer"; this.container = $("#localVideoContainer").get(0); this.localVideoId = null; if(config.enableLocalVideoFlip) this._buildContextMenu(); this.isLocal = true; this.emitter = emitter; Object.defineProperty(this, 'id', { get: function () { return APP.conference.getMyUserId(); } }); this.initBrowserSpecificProperties(); SmallVideo.call(this, VideoLayout); // Set default display name. this.setDisplayName(); this.createConnectionIndicator(); } LocalVideo.prototype = Object.create(SmallVideo.prototype); LocalVideo.prototype.constructor = LocalVideo; /** * Creates the edit display name button. * * @returns {object} the edit button */ function createEditDisplayNameButton() { var editButton = document.createElement('a'); editButton.className = 'displayname'; UIUtil.setTooltip(editButton, "videothumbnail.editnickname", "left"); editButton.innerHTML = ''; return editButton; } /** * Sets the display name for the given video span id. */ LocalVideo.prototype.setDisplayName = function(displayName, key) { if (!this.container) { console.warn( "Unable to set displayName - " + this.videoSpanId + " does not exist"); return; } var nameSpan = $('#' + this.videoSpanId + ' .displayname'); var defaultLocalDisplayName = APP.translation.generateTranslationHTML( interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME); var meHTML; // If we already have a display name for this video. if (nameSpan.length > 0) { if (nameSpan.text() !== displayName) { if (displayName && displayName.length > 0) { meHTML = APP.translation.generateTranslationHTML("me"); $('#localDisplayName').html( `${UIUtil.escapeHtml(displayName)} (${meHTML})` ); } else { $('#localDisplayName').html(defaultLocalDisplayName); } } this.updateView(); } else { nameSpan = document.createElement('span'); nameSpan.className = 'displayname'; document.getElementById(this.videoSpanId) .querySelector('.videocontainer__toolbar') .appendChild(nameSpan); if (displayName && displayName.length > 0) { meHTML = APP.translation.generateTranslationHTML("me"); nameSpan.innerHTML = UIUtil.escapeHtml(displayName) + meHTML; } else { nameSpan.innerHTML = defaultLocalDisplayName; } nameSpan.id = 'localDisplayName'; //translates popover of edit button APP.translation.translateElement($("a.displayname")); var editableText = document.createElement('input'); editableText.className = 'editdisplayname'; editableText.type = 'text'; editableText.id = 'editDisplayName'; if (displayName && displayName.length) { editableText.value = displayName; } var defaultNickname = APP.translation.translateString( "defaultNickname", {name: "Jane Pink"}); editableText.setAttribute('style', 'display:none;'); editableText.setAttribute('data-18n', '[placeholder]defaultNickname'); editableText.setAttribute("data-i18n-options", JSON.stringify({name: "Jane Pink"})); editableText.setAttribute("placeholder", defaultNickname); this.container .querySelector('.videocontainer__toolbar') .appendChild(editableText); var self = this; $('#localVideoContainer .displayname') .bind("click", function (e) { let $editDisplayName = $('#editDisplayName'); let $localDisplayName = $('#localDisplayName'); e.preventDefault(); e.stopPropagation(); $localDisplayName.hide(); $editDisplayName.show(); $editDisplayName.focus(); $editDisplayName.select(); $editDisplayName.one("focusout", function (e) { self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value); $editDisplayName.hide(); $localDisplayName.show(); }); $editDisplayName.on('keydown', function (e) { if (e.keyCode === 13) { e.preventDefault(); $('#editDisplayName').hide(); // focusout handler will save display name } }); }); } }; LocalVideo.prototype.createConnectionIndicator = function() { if(this.connectionIndicator) return; this.connectionIndicator = new ConnectionIndicator(this, null); }; LocalVideo.prototype.changeVideo = function (stream) { this.videoStream = stream; let localVideoClick = (event) => { // FIXME: with Temasys plugin event arg is not an event, but // the clicked object itself, so we have to skip this call if (event.stopPropagation) { event.stopPropagation(); } this.VideoLayout.handleVideoThumbClicked(this.id); }; let localVideoContainerSelector = $('#localVideoContainer'); localVideoContainerSelector.off('click'); localVideoContainerSelector.on('click', localVideoClick); let localVideo = document.createElement('video'); localVideo.id = this.localVideoId = 'localVideo_' + stream.getId(); RTCUIUtils.setAutoPlay(localVideo, true); RTCUIUtils.setVolume(localVideo, 0); var localVideoContainer = document.getElementById('localVideoWrapper'); // Put the new video always in front UIUtil.prependChild(localVideoContainer, localVideo); // Add click handler to both video and video wrapper elements in case // there's no video. // onclick has to be used with Temasys plugin localVideo.onclick = localVideoClick; let isVideo = stream.videoType != "desktop"; this._enableDisableContextMenu(isVideo); this.setFlipX(isVideo? APP.settings.getLocalFlipX() : false); // Attach WebRTC stream localVideo = stream.attach(localVideo); let endedHandler = () => { localVideoContainer.removeChild(localVideo); // when removing only the video element and we are on stage // update the stage if(this.isCurrentlyOnLargeVideo()) this.VideoLayout.updateLargeVideo(this.id); stream.off(TrackEvents.LOCAL_TRACK_STOPPED, endedHandler); }; stream.on(TrackEvents.LOCAL_TRACK_STOPPED, endedHandler); }; /** * Shows or hides the local video container. * @param {boolean} true to make the local video container visible, false * otherwise */ LocalVideo.prototype.setVisible = function(visible) { // We toggle the hidden class as an indication to other interested parties // that this container has been hidden on purpose. $("#localVideoContainer").toggleClass("hidden"); // We still show/hide it as we need to overwrite the style property if we // want our action to take effect. Toggling the display property through // the above css class didn't succeed in overwriting the style. if (visible) { $("#localVideoContainer").show(); } else { $("#localVideoContainer").hide(); } }; /** * Sets the flipX state of the video. * @param val {boolean} true for flipped otherwise false; */ LocalVideo.prototype.setFlipX = function (val) { this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val); if(!this.localVideoId) return; if(val) { this.selectVideoElement().addClass("flipVideoX"); } else { this.selectVideoElement().removeClass("flipVideoX"); } }; /** * Builds the context menu for the local video. */ LocalVideo.prototype._buildContextMenu = function () { $.contextMenu({ selector: '#' + this.videoSpanId, zIndex: 10000, items: { flip: { name: "Flip", callback: () => { let val = !APP.settings.getLocalFlipX(); this.setFlipX(val); APP.settings.setLocalFlipX(val); } } }, events: { show : function(options){ options.items.flip.name = APP.translation.translateString("videothumbnail.flip"); } } }); }; /** * Enables or disables the context menu for the local video. * @param enable {boolean} true for enable, false for disable */ LocalVideo.prototype._enableDisableContextMenu = function (enable) { if($('#' + this.videoSpanId).contextMenu) $('#' + this.videoSpanId).contextMenu(enable); }; export default LocalVideo;