Merge pull request #543 from damencho/shared-video

Shared video, synchronized play/pause/seek/muting/volume, initial commit.
This commit is contained in:
yanas 2016-03-22 15:15:36 -05:00
commit 32c2d912be
14 changed files with 716 additions and 27 deletions

View File

@ -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);
}
});
}
};

View File

@ -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 {

View File

@ -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;

View File

@ -123,6 +123,7 @@
<span id="unreadMessages"></span>
</a>
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
<a class="button fa fa-share-alt-square" id="toolbar_button_sharedvideo" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared Video" data-i18n="[content]toolbar.sharedvideo" style="display: none"></a>
<a class="button icon-share-desktop" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleDesktopSharingPopover" content="Share screen" data-i18n="[content]toolbar.sharescreen" style="display: none"></a>
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen"></a>
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
@ -137,6 +138,7 @@
<div id="largeVideoContainer" class="videocontainer">
<div id="presentation"></div>
<div id="sharedVideo"><div id="sharedVideoIFrame"></div></div>
<div id="etherpad"></div>
<a target="_new"><div class="watermark leftwatermark"></div></a>
<a target="_new"><div class="watermark rightwatermark"></div></a>

View File

@ -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

View File

@ -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,10 @@
"passwordRequired": "Password required",
"Ok": "Ok",
"Remove": "Remove",
"shareVideoTitle": "Share a video",
"shareVideoLinkError": "Please provide a correct youtube link.",
"removeSharedVideoTitle": "Remove shared video",
"removeSharedVideoMsg": "Are you sure you would like to remove your shared video?",
"WaitingForHost": "Waiting for the host ...",
"WaitForHostMsg": "The conference <b>__room__ </b> 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",

View File

@ -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 () {
@ -481,11 +490,11 @@ UI.addUser = function (id, displayName) {
config.startAudioMuted > APP.conference.membersCount)
UIUtil.playSoundNotification('userJoined');
// Configure avatar
UI.setUserAvatar(id);
// Add Peer's container
VideoLayout.addParticipantContainer(id);
// Configure avatar
UI.setUserAvatar(id);
};
/**
@ -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;

View File

@ -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 () {

View File

@ -0,0 +1,537 @@
/* 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;
}
showStopVideoPropmpt().then(() =>
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(true);
}
};
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 we are sending the command and we are starting the player
// we need to continuously send the player current time position
if(APP.conference.isLocalId(self.from)) {
self.intervalId = setInterval(
self.updateCheck.bind(self),
self.updateInterval);
}
// set initial state of the player if there is enough information
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(sendPauseEvent)
{
// 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 && sendPauseEvent) {
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
|| !this.player.pauseVideo
|| !this.player.playVideo
|| !this.player.getVolume
|| !this.player.seekTo
|| !this.player.getVolume)
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);
};
/**
* Removes RemoteVideo from the page.
*/
SharedVideoThumb.prototype.remove = function () {
console.log("Remove shared video thumb", this.id);
// Make sure that the large video is updated if are removing its
// corresponding small video.
this.VideoLayout.updateRemovedVideo(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 if he want to close shared video.
*/
function showStopVideoPropmpt() {
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
"dialog.removeSharedVideoTitle",
null,
"dialog.removeSharedVideoMsg",
null,
false,
"dialog.Remove",
function(e,v,m,f) {
if (v) {
resolve();
} else {
reject();
}
}
);
});
}
/**
* 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: `
<h2>${title}</h2>
<input name="sharedVideoUrl" type="text"
data-i18n="[placeholder]defaultLink"
data-i18n-options="${JSON.stringify(i18nOptions)}"
placeholder="${defaultUrl}"
autofocus>`,
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: `<h2>${title}</h2> ${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');
}
}
}
});
});
}

View File

@ -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 () {

View File

@ -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);
@ -451,7 +452,12 @@ export default class LargeVideoManager {
// change the avatar url on large
this.updateAvatar(Avatar.getAvatarUrl(id));
let isVideoMuted = stream ? stream.isMuted() : true;
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
// its stream whether exist and is muted to set isVideoMuted
// in rest of the cases it is false
let isVideoMuted = false;
if (videoType == VIDEO_CONTAINER_TYPE)
isVideoMuted = stream ? stream.isMuted() : true;
// show the avatar on large if needed
container.showAvatar(isVideoMuted);
@ -616,7 +622,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 +631,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);
}
});

View File

@ -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) {

View File

@ -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";
@ -92,6 +93,11 @@ var VideoLayout = {
init (emitter) {
eventEmitter = emitter;
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
// sets default video type of local video
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
// if we do not resize the thumbs here, if there is no video device
// the local video thumb maybe one pixel
this.resizeThumbnails(false, true, false);
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
this.lastNCount = config.channelLastN;
@ -343,7 +349,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 +373,8 @@ var VideoLayout = {
// current dominant, focused speaker or update it to
// the current dominant speaker.
if ((!focusedVideoResourceJid &&
!currentDominantSpeaker) ||
!currentDominantSpeaker &&
this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE)) ||
focusedVideoResourceJid === resourceJid ||
(resourceJid &&
currentDominantSpeaker === resourceJid)) {
@ -888,7 +895,7 @@ var VideoLayout = {
},
isLargeVideoVisible () {
return this.isLargeContainerTypeVisible(VideoContainerType);
return this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE);
},
/**
@ -960,8 +967,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 +986,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;
}
};

View File

@ -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",