diff --git a/conference.js b/conference.js index 57a660156..ad08b8a53 100644 --- a/conference.js +++ b/conference.js @@ -27,7 +27,8 @@ let room, connection, localAudio, localVideo, roomLocker; const Commands = { CONNECTION_QUALITY: "stats", EMAIL: "email", - ETHERPAD: "etherpad" + ETHERPAD: "etherpad", + SHARED_VIDEO: "shared-video" }; /** @@ -689,6 +690,7 @@ export default { console.log('USER %s LEFT', id, user); APP.API.notifyUserLeft(id); APP.UI.removeUser(id, user.getDisplayName()); + APP.UI.stopSharedVideo({from: id}); }); @@ -1012,5 +1014,47 @@ export default { APP.UI.addListener( UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this) ); + + APP.UI.addListener(UIEvents.UPDATE_SHARED_VIDEO, + (url, state, time, volume) => { + // send start and stop commands once, and remove any updates + // that had left + if (state === 'stop' || state === 'start' || state === 'playing') { + room.removeCommand(Commands.SHARED_VIDEO); + room.sendCommandOnce(Commands.SHARED_VIDEO, { + value: url, + attributes: { + from: APP.conference.localId, + state: state, + time: time, + volume: volume + } + }); + } + else { + // in case of paused, in order to allow late users to join + // paused + room.sendCommand(Commands.SHARED_VIDEO, { + value: url, + attributes: { + from: APP.conference.localId, + state: state, + time: time, + volume: volume + } + }); + } + }); + room.addCommandListener( + Commands.SHARED_VIDEO, ({value, attributes}) => { + if (attributes.state === 'stop') { + APP.UI.stopSharedVideo(attributes); + } else if (attributes.state === 'start') { + APP.UI.showSharedVideo(value, attributes); + } else if (attributes.state === 'playing' + || attributes.state === 'pause') { + APP.UI.updateSharedVideo(value, attributes); + } + }); } }; diff --git a/css/main.css b/css/main.css index 5a937025f..a79958423 100644 --- a/css/main.css +++ b/css/main.css @@ -63,6 +63,7 @@ html, body{ text-align: center; text-shadow: 0 1px 0 rgba(255,255,255,.3), 0 -1px 0 rgba(0,0,0,.6); z-index: 1; + font-size: 1.22em !important; } .toolbar_span>span { diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 73668cf56..0469afb9c 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -31,6 +31,7 @@ position: relative; margin-left: auto; margin-right: auto; + text-align: center; } #remoteVideos .videocontainer { @@ -112,6 +113,7 @@ } #presentation, +#sharedVideo, #etherpad, #localVideoWrapper>video, #localVideoWrapper>object, @@ -436,6 +438,10 @@ border-radius: 200px; } +.sharedVideoAvatar { + height: 100%; +} + .noMic { position: absolute; border-radius: 8px; diff --git a/index.html b/index.html index 4154cbdc5..6c8a831db 100644 --- a/index.html +++ b/index.html @@ -123,6 +123,7 @@ + @@ -137,6 +138,7 @@
+
diff --git a/interface_config.js b/interface_config.js index 7101d6d05..1f6754bd1 100644 --- a/interface_config.js +++ b/interface_config.js @@ -16,7 +16,7 @@ var interfaceConfig = { INVITATION_POWERED_BY: true, DOMINANT_SPEAKER_AVATAR_SIZE: 100, TOOLBAR_BUTTONS: ['authentication', 'microphone', 'camera', 'desktop', - 'recording', 'security', 'invite', 'chat', 'etherpad', + 'recording', 'security', 'invite', 'chat', 'etherpad', 'sharedvideo', 'fullscreen', 'sip', 'dialpad', 'settings', 'hangup', 'filmstrip', 'contacts'], // Determines how the video would fit the screen. 'both' would fit the whole diff --git a/lang/main.json b/lang/main.json index 6eaf7e18e..12d579de8 100644 --- a/lang/main.json +++ b/lang/main.json @@ -9,6 +9,7 @@ "me": "me", "speaker": "Speaker", "defaultNickname": "ex. __name__", + "defaultLink": "e.g. __url__", "welcomepage":{ "go": "GO", "roomname": "Enter room name", @@ -55,6 +56,7 @@ "invite": "Invite others", "chat": "Open / close chat", "etherpad": "Shared document", + "sharedvideo": "Shared video", "sharescreen": "Share screen", "fullscreen": "Enter / Exit Full Screen", "sip": "Call SIP number", @@ -159,6 +161,8 @@ "passwordRequired": "Password required", "Ok": "Ok", "Remove": "Remove", + "shareVideoTitle": "Share a video", + "shareVideoLinkError": "Please provide a correct youtube link.", "WaitingForHost": "Waiting for the host ...", "WaitForHostMsg": "The conference __room__ has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.", "IamHost": "I am the host", diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 8cfa0e067..fcd39c4fc 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -13,6 +13,7 @@ import UIUtil from "./util/UIUtil"; import UIEvents from "../../service/UI/UIEvents"; import CQEvents from '../../service/connectionquality/CQEvents'; import EtherpadManager from './etherpad/Etherpad'; +import SharedVideoManager from './shared_video/SharedVideo'; import VideoLayout from "./videolayout/VideoLayout"; import FilmStrip from "./videolayout/FilmStrip"; @@ -30,6 +31,7 @@ var eventEmitter = new EventEmitter(); UI.eventEmitter = eventEmitter; let etherpadManager; +let sharedVideoManager; /** * Prompt user for nickname. @@ -260,6 +262,12 @@ function registerListeners() { } }); + UI.addListener(UIEvents.SHARED_VIDEO_CLICKED, function () { + if (sharedVideoManager) { + sharedVideoManager.toggleSharedVideo(); + } + }); + UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen); UI.addListener(UIEvents.TOGGLE_CHAT, UI.toggleChat); @@ -334,6 +342,7 @@ UI.start = function () { ContactList.init(eventEmitter); bindEvents(); + sharedVideoManager = new SharedVideoManager(eventEmitter); if (!interfaceConfig.filmStripOnly) { $("#videospace").mousemove(function () { @@ -530,6 +539,7 @@ UI.updateLocalRole = function (isModerator) { Toolbar.showSipCallButton(isModerator); Toolbar.showRecordingButton(isModerator); + Toolbar.showSharedVideoButton(isModerator); SettingsMenu.showStartMutedOptions(isModerator); if (isModerator) { @@ -1030,6 +1040,14 @@ UI.getLargeVideoID = function () { return VideoLayout.getLargeVideoID(); }; +/** + * Returns the current video shown on large. + * Currently used by tests (torture). + */ +UI.getLargeVideo = function () { + return VideoLayout.getLargeVideo(); +}; + /** * Shows dialog with a link to FF extension. */ @@ -1046,4 +1064,33 @@ UI.updateDevicesAvailability = function (id, devices) { VideoLayout.setDeviceAvailabilityIcons(id, devices); }; +/** +* Show shared video. +* @param {string} url video url +* @param {string} attributes +*/ +UI.showSharedVideo = function (url, attributes) { + if (sharedVideoManager) + sharedVideoManager.showSharedVideo(url, attributes); +}; + +/** + * Update shared video. + * @param {string} url video url + * @param {string} attributes + */ +UI.updateSharedVideo = function (url, attributes) { + if (sharedVideoManager) + sharedVideoManager.updateSharedVideo(url, attributes); +}; + +/** + * Stop showing shared video. + * @param {string} attributes + */ +UI.stopSharedVideo = function (attributes) { + if (sharedVideoManager) + sharedVideoManager.stopSharedVideo(attributes); +}; + module.exports = UI; diff --git a/modules/UI/etherpad/Etherpad.js b/modules/UI/etherpad/Etherpad.js index 01979eb07..7c65016a5 100644 --- a/modules/UI/etherpad/Etherpad.js +++ b/modules/UI/etherpad/Etherpad.js @@ -110,9 +110,11 @@ class Etherpad extends LargeContainer { show () { const $iframe = $(this.iframe); const $container = $(this.container); + let self = this; return new Promise(resolve => { $iframe.fadeIn(300, function () { + self.bodyBackground = document.body.style.background; document.body.style.background = '#eeeeee'; $iframe.css({visibility: 'visible'}); $container.css({zIndex: 2}); @@ -124,6 +126,7 @@ class Etherpad extends LargeContainer { hide () { const $iframe = $(this.iframe); const $container = $(this.container); + document.body.style.background = this.bodyBackground; return new Promise(resolve => { $iframe.fadeOut(300, function () { diff --git a/modules/UI/shared_video/SharedVideo.js b/modules/UI/shared_video/SharedVideo.js new file mode 100644 index 000000000..5b7d16620 --- /dev/null +++ b/modules/UI/shared_video/SharedVideo.js @@ -0,0 +1,503 @@ +/* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */ + +import messageHandler from '../util/MessageHandler'; +import UIUtil from '../util/UIUtil'; +import UIEvents from '../../../service/UI/UIEvents'; + +import VideoLayout from "../videolayout/VideoLayout"; +import LargeContainer from '../videolayout/LargeContainer'; +import SmallVideo from '../videolayout/SmallVideo'; +import FilmStrip from '../videolayout/FilmStrip'; +import ToolbarToggler from "../toolbars/ToolbarToggler"; + +export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo"; + +/** + * Example shared video link. + * @type {string} + */ +const defaultSharedVideoLink = "https://www.youtube.com/watch?v=xNXN7CZk8X0"; + +/** + * Manager of shared video. + */ +export default class SharedVideoManager { + constructor (emitter) { + this.emitter = emitter; + this.isSharedVideoShown = false; + this.isPlayerAPILoaded = false; + this.updateInterval = 5000; // milliseconds + } + + /** + * Starts shared video by asking user for url, or if its already working + * asks whether the user wants to stop sharing the video. + */ + toggleSharedVideo () { + if(!this.isSharedVideoShown) { + requestVideoLink().then( + url => this.emitter.emit( + UIEvents.UPDATE_SHARED_VIDEO, url, 'start'), + err => console.error('SHARED VIDEO CANCELED', err) + ); + return; + } + + this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, null, 'stop'); + } + + /** + * Shows the player component and starts the checking function + * that will be sending updates, if we are the one shared the video + * @param url the video url + * @param attributes + */ + showSharedVideo (url, attributes) { + if (this.isSharedVideoShown) + return; + + // the video url + this.url = url; + + // the owner of the video + this.from = attributes.from; + + // This code loads the IFrame Player API code asynchronously. + var tag = document.createElement('script'); + + tag.src = "https://www.youtube.com/iframe_api"; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + var self = this; + if(self.isPlayerAPILoaded) + window.onYouTubeIframeAPIReady(); + else + window.onYouTubeIframeAPIReady = function() { + self.isPlayerAPILoaded = true; + let showControls = APP.conference.isLocalId(self.from) ? 1 : 0; + self.player = new YT.Player('sharedVideoIFrame', { + height: '100%', + width: '100%', + videoId: self.url, + playerVars: { + 'origin': location.origin, + 'fs': '0', + 'autoplay': 1, + 'controls': showControls, + 'rel' : 0 + }, + events: { + 'onReady': onPlayerReady, + 'onStateChange': onPlayerStateChange, + 'onError': onPlayerError + } + }); + }; + + window.onPlayerStateChange = function(event) { + if (event.data == YT.PlayerState.PLAYING) { + self.playerPaused = false; + self.updateCheck(); + } else if (event.data == YT.PlayerState.PAUSED) { + self.playerPaused = true; + self.updateCheck(); + } + }; + + window.onPlayerReady = function(event) { + let player = event.target; + player.playVideo(); + + let thumb = new SharedVideoThumb(self.url); + thumb.setDisplayName(player.getVideoData().title); + VideoLayout.addParticipantContainer(self.url, thumb); + + let iframe = player.getIframe(); + self.sharedVideo = new SharedVideoContainer( + {url, iframe, player}); + + VideoLayout.addLargeVideoContainer( + SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo); + VideoLayout.handleVideoThumbClicked(true, self.url); + + self.isSharedVideoShown = true; + + if(APP.conference.isLocalId(self.from)) { + self.intervalId = setInterval( + self.updateCheck.bind(self), + self.updateInterval); + } + + // set initial state + if(attributes.state === 'pause') + player.pauseVideo(); + else if(attributes.time > 0) { + console.log("Player seekTo:", attributes.time); + player.seekTo(attributes.time); + } + }; + + window.onPlayerError = function(event) { + console.error("Error in the player:" + event.data); + }; + } + + /** + * Checks current state of the player and fire an event with the values. + */ + updateCheck() + { + // ignore update checks if we are not the owner of the video + if(!APP.conference.isLocalId(this.from)) + return; + + let state = this.player.getPlayerState(); + // if its paused and haven't been pause - send paused + if (state === YT.PlayerState.PAUSED && !this.playerPaused) { + this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, + this.url, 'pause'); + } + // if its playing and it was paused - send update with time + // if its playing and was playing just send update with time + else if (state === YT.PlayerState.PLAYING) { + this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, + this.url, 'playing', + this.player.getCurrentTime(), + this.player.isMuted() ? 0 : this.player.getVolume()); + } + } + + /** + * Updates video, if its not playing and needs starting or + * if its playing and needs to be paysed + * @param url the video url + * @param attributes + */ + updateSharedVideo (url, attributes) { + // if we are sending the event ignore + if(APP.conference.isLocalId(this.from)) { + return; + } + + if (attributes.state == 'playing') { + + if(!this.isSharedVideoShown) { + this.showSharedVideo(url, attributes); + return; + } + + // ocasionally we get this.player.getCurrentTime is not a function + // it seems its that player hasn't really loaded + if(!this.player || !this.player.getCurrentTime) + return; + + // check received time and current time + let currentPosition = this.player.getCurrentTime(); + let diff = Math.abs(attributes.time - currentPosition); + + // if we drift more than two times of the interval for checking + // sync, the interval is in milliseconds + if(diff > this.updateInterval*2/1000) { + console.log("Player seekTo:", attributes.time, + " current time is:", currentPosition, " diff:", diff); + this.player.seekTo(attributes.time); + } + + // lets check the volume + if (attributes.volume !== undefined && + this.player.getVolume() != attributes.volume) { + this.player.setVolume(attributes.volume); + console.log("Player change of volume:" + attributes.volume); + } + + + if(this.playerPaused) + this.player.playVideo(); + } else if (attributes.state == 'pause') { + // if its not paused, pause it + if(this.isSharedVideoShown) { + this.player.pauseVideo(); + } + else { + // if not shown show it, passing attributes so it can + // be shown paused + this.showSharedVideo(url, attributes); + } + } + } + + /** + * Stop shared video if it is currently showed. If the user started the + * shared video is the one in the attributes.from (called when user + * left and we want to remove video if the user sharing it left). + * @param attributes + */ + stopSharedVideo (attributes) { + if (!this.isSharedVideoShown) + return; + + if(this.from !== attributes.from) + return; + + if(this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + + VideoLayout.removeParticipantContainer(this.url); + + VideoLayout.showLargeVideoContainer(SHARED_VIDEO_CONTAINER_TYPE, false) + .then(() => { + VideoLayout.removeLargeVideoContainer( + SHARED_VIDEO_CONTAINER_TYPE); + + this.player.destroy(); + this.player = null; + }); + + this.url = null; + this.isSharedVideoShown = false; + } +} + +/** + * Container for shared video iframe. + */ +class SharedVideoContainer extends LargeContainer { + + constructor ({url, iframe, player}) { + super(); + + this.$iframe = $(iframe); + this.url = url; + this.player = player; + } + + get $video () { + return this.$iframe; + } + + show () { + return new Promise(resolve => { + this.$iframe.fadeIn(300, () => { + this.$iframe.css({opacity: 1}); + resolve(); + }); + }); + } + + hide () { + return new Promise(resolve => { + this.$iframe.fadeOut(300, () => { + this.$iframe.css({opacity: 0}); + resolve(); + }); + }); + } + + onHoverIn () { + ToolbarToggler.showToolbar(); + } + + get id () { + return this.url; + } + + resize (containerWidth, containerHeight) { + let height = containerHeight - FilmStrip.getFilmStripHeight(); + + let width = containerWidth; + + this.$iframe.width(width).height(height); + } + + /** + * @return {boolean} do not switch on dominant speaker event if on stage. + */ + stayOnStage () { + return false; + } +} + +function SharedVideoThumb (url) +{ + this.id = url; + + this.url = url; + this.setVideoType(SHARED_VIDEO_CONTAINER_TYPE); + this.videoSpanId = "sharedVideoContainer"; + this.container = this.createContainer(this.videoSpanId); + this.container.onclick = this.videoClick.bind(this); + //this.bindHoverHandler(); + + SmallVideo.call(this, VideoLayout); + this.isVideoMuted = true; +} +SharedVideoThumb.prototype = Object.create(SmallVideo.prototype); +SharedVideoThumb.prototype.constructor = SharedVideoThumb; + +/** + * hide display name + */ + +SharedVideoThumb.prototype.setDeviceAvailabilityIcons = function () {}; + +SharedVideoThumb.prototype.avatarChanged = function () {}; + +SharedVideoThumb.prototype.createContainer = function (spanId) { + var container = document.createElement('span'); + container.id = spanId; + container.className = 'videocontainer'; + + // add the avatar + var avatar = document.createElement('img'); + avatar.id = 'avatar_' + this.id; + avatar.className = 'sharedVideoAvatar'; + avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg"; + container.appendChild(avatar); + + var remotes = document.getElementById('remoteVideos'); + return remotes.appendChild(container); +}; + +/** + * The thumb click handler. + */ +SharedVideoThumb.prototype.videoClick = function () { + VideoLayout.handleVideoThumbClicked(true, this.url); + VideoLayout.showLargeVideoContainer(this.videoType, true); +}; + +/** + * Removes RemoteVideo from the page. + */ +SharedVideoThumb.prototype.remove = function () { + console.log("Remove shared video thumb", this.id); + + // Remove whole container + if (this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } +}; + +/** + * Sets the display name for the thumb. + */ +SharedVideoThumb.prototype.setDisplayName = function(displayName) { + if (!this.container) { + console.warn( "Unable to set displayName - " + this.videoSpanId + + " does not exist"); + return; + } + + var nameSpan = $('#' + this.videoSpanId + '>span.displayname'); + + // If we already have a display name for this video. + if (nameSpan.length > 0) { + if (displayName && displayName.length > 0) { + $('#' + this.videoSpanId + '_name').text(displayName); + } + } else { + nameSpan = document.createElement('span'); + nameSpan.className = 'displayname'; + $('#' + this.videoSpanId)[0].appendChild(nameSpan); + + if (displayName && displayName.length > 0) + $(nameSpan).text(displayName); + nameSpan.id = this.videoSpanId + '_name'; + } + +}; + +/** + * Checks if given string is youtube url. + * @param {string} url string to check. + * @returns {boolean} + */ +function getYoutubeLink(url) { + let p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;//jshint ignore:line + return (url.match(p)) ? RegExp.$1 : false; +} + +/** + * Ask user for shared video url to share with others. + * Dialog validates client input to allow only youtube urls. + */ +function requestVideoLink() { + let i18n = APP.translation; + const title = i18n.generateTranslationHTML("dialog.shareVideoTitle"); + const cancelButton = i18n.generateTranslationHTML("dialog.Cancel"); + const shareButton = i18n.generateTranslationHTML("dialog.Share"); + const backButton = i18n.generateTranslationHTML("dialog.Back"); + const linkError + = i18n.generateTranslationHTML("dialog.shareVideoLinkError"); + const i18nOptions = {url: defaultSharedVideoLink}; + const defaultUrl = i18n.translateString("defaultLink", i18nOptions); + + return new Promise(function (resolve, reject) { + let dialog = messageHandler.openDialogWithStates({ + state0: { + html: ` +

${title}

+ `, + persistent: false, + buttons: [ + {title: cancelButton, value: false}, + {title: shareButton, value: true} + ], + focus: ':input:first', + defaultButton: 1, + submit: function (e, v, m, f) { + e.preventDefault(); + if (!v) { + reject('cancelled'); + dialog.close(); + return; + } + + let sharedVideoUrl = f.sharedVideoUrl; + if (!sharedVideoUrl) { + return; + } + + let urlValue = encodeURI(UIUtil.escapeHtml(sharedVideoUrl)); + let yVideoId = getYoutubeLink(urlValue); + if (!yVideoId) { + dialog.goToState('state1'); + return false; + } + + resolve(yVideoId); + dialog.close(); + } + }, + + state1: { + html: `

${title}

${linkError}`, + persistent: false, + buttons: [ + {title: cancelButton, value: false}, + {title: backButton, value: true} + ], + focus: ':input:first', + defaultButton: 1, + submit: function (e, v, m, f) { + e.preventDefault(); + if (v === 0) { + reject(); + dialog.close(); + } else { + dialog.goToState('state0'); + } + } + } + }); + + }); +} + diff --git a/modules/UI/toolbars/Toolbar.js b/modules/UI/toolbars/Toolbar.js index 992af2d22..890b13bb2 100644 --- a/modules/UI/toolbars/Toolbar.js +++ b/modules/UI/toolbars/Toolbar.js @@ -126,6 +126,10 @@ const buttonHandlers = { AnalyticsAdapter.sendEvent('toolbar.etherpad.clicked'); emitter.emit(UIEvents.ETHERPAD_CLICKED); }, + "toolbar_button_sharedvideo": function () { + AnalyticsAdapter.sendEvent('toolbar.sharedvideo.clicked'); + emitter.emit(UIEvents.SHARED_VIDEO_CLICKED); + }, "toolbar_button_desktopsharing": function () { if (APP.conference.isSharingScreen) { AnalyticsAdapter.sendEvent('toolbar.screen.disabled'); @@ -284,6 +288,15 @@ const Toolbar = { } }, + // Shows or hides the 'shared video' button. + showSharedVideoButton (show) { + if (UIUtil.isButtonEnabled('sharedvideo') && show) { + $('#toolbar_button_sharedvideo').css({display: "inline-block"}); + } else { + $('#toolbar_button_sharedvideo').css({display: "none"}); + } + }, + // checks whether recording is enabled and whether we have params // to start automatically recording checkAutoRecord () { diff --git a/modules/UI/videolayout/LargeVideo.js b/modules/UI/videolayout/LargeVideo.js index 494062af9..61a2d46c4 100644 --- a/modules/UI/videolayout/LargeVideo.js +++ b/modules/UI/videolayout/LargeVideo.js @@ -11,6 +11,8 @@ import {createDeferred} from '../../util/helpers'; const avatarSize = interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE; const FADE_DURATION_MS = 300; +export const VIDEO_CONTAINER_TYPE = "camera"; + /** * Get stream id. * @param {JitsiTrack?} stream @@ -150,8 +152,6 @@ function getDesktopVideoPosition(videoWidth, return { horizontalIndent, verticalIndent }; } -export const VideoContainerType = "camera"; - /** * Container for user video. */ @@ -365,9 +365,10 @@ export default class LargeVideoManager { constructor () { this.containers = {}; - this.state = VideoContainerType; - this.videoContainer = new VideoContainer(() => this.resizeContainer(VideoContainerType)); - this.addContainer(VideoContainerType, this.videoContainer); + this.state = VIDEO_CONTAINER_TYPE; + this.videoContainer = new VideoContainer( + () => this.resizeContainer(VIDEO_CONTAINER_TYPE)); + this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer); // use the same video container to handle and desktop tracks this.addContainer("desktop", this.videoContainer); @@ -616,7 +617,7 @@ export default class LargeVideoManager { } let oldContainer = this.containers[this.state]; - if (this.state === VideoContainerType) { + if (this.state === VIDEO_CONTAINER_TYPE) { this.showWatermark(false); } oldContainer.hide(); @@ -625,7 +626,7 @@ export default class LargeVideoManager { let container = this.getContainer(type); return container.show().then(() => { - if (type === VideoContainerType) { + if (type === VIDEO_CONTAINER_TYPE) { this.showWatermark(true); } }); diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index fa5bac0bf..055bc460d 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -363,14 +363,7 @@ SmallVideo.prototype.updateView = function () { } setVisibility(avatar, showAvatar); - var showDisplayName = !showVideo && !showAvatar; - - if (showDisplayName) { - this.showDisplayName(this.VideoLayout.isLargeVideoVisible()); - } - else { - this.showDisplayName(false); - } + this.showDisplayName(!showVideo && !showAvatar); }; SmallVideo.prototype.avatarChanged = function (avatarUrl) { diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 22e6e16ef..e5b4130b3 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -9,7 +9,8 @@ import UIEvents from "../../../service/UI/UIEvents"; import UIUtil from "../util/UIUtil"; import RemoteVideo from "./RemoteVideo"; -import LargeVideoManager, {VideoContainerType} from "./LargeVideo"; +import LargeVideoManager, {VIDEO_CONTAINER_TYPE} from "./LargeVideo"; +import {SHARED_VIDEO_CONTAINER_TYPE} from '../shared_video/SharedVideo'; import LocalVideo from "./LocalVideo"; import PanelToggler from "../side_pannels/SidePanelToggler"; @@ -343,7 +344,7 @@ var VideoLayout = { let videoType = VideoLayout.getRemoteVideoType(id); if (!videoType) { // make video type the default one (camera) - videoType = VideoContainerType; + videoType = VIDEO_CONTAINER_TYPE; } remoteVideo.setVideoType(videoType); @@ -367,7 +368,8 @@ var VideoLayout = { // current dominant, focused speaker or update it to // the current dominant speaker. if ((!focusedVideoResourceJid && - !currentDominantSpeaker) || + !currentDominantSpeaker && + !this.isLargeContainerTypeVisible(SHARED_VIDEO_CONTAINER_TYPE)) || focusedVideoResourceJid === resourceJid || (resourceJid && currentDominantSpeaker === resourceJid)) { @@ -888,7 +890,7 @@ var VideoLayout = { }, isLargeVideoVisible () { - return this.isLargeContainerTypeVisible(VideoContainerType); + return this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE); }, /** @@ -960,8 +962,17 @@ var VideoLayout = { return Promise.resolve(); } + let currentId = largeVideo.id; + if(currentId) { + var oldSmallVideo = this.getSmallVideo(currentId); + } + // if !show then use default type - large video - return largeVideo.showContainer(show ? type : VideoContainerType); + return largeVideo.showContainer(show ? type : VIDEO_CONTAINER_TYPE) + .then(() => { + if(oldSmallVideo) + oldSmallVideo && oldSmallVideo.updateView(); + }); }, isLargeContainerTypeVisible (type) { @@ -970,10 +981,18 @@ var VideoLayout = { /** * Returns the id of the current video shown on large. - * Currently used by tests (troture). + * Currently used by tests (torture). */ getLargeVideoID () { return largeVideo.id; + }, + + /** + * Returns the the current video shown on large. + * Currently used by tests (torture). + */ + getLargeVideo () { + return largeVideo; } }; diff --git a/service/UI/UIEvents.js b/service/UI/UIEvents.js index cc507164c..5444ebb0a 100644 --- a/service/UI/UIEvents.js +++ b/service/UI/UIEvents.js @@ -21,6 +21,13 @@ export default { AUDIO_MUTED: "UI.audio_muted", VIDEO_MUTED: "UI.video_muted", ETHERPAD_CLICKED: "UI.etherpad_clicked", + SHARED_VIDEO_CLICKED: "UI.start-shared-video", + /** + * Updates shared video with params: url, state, time(optional) + * Where url is the video link, state is stop/start/pause and time is the + * current video playing time. + */ + UPDATE_SHARED_VIDEO: "UI.update-shared-video", ROOM_LOCK_CLICKED: "UI.room_lock_clicked", USER_INVITED: "UI.user_invited", USER_KICKED: "UI.user_kicked",