Merge pull request #543 from damencho/shared-video
Shared video, synchronized play/pause/seek/muting/volume, initial commit.
This commit is contained in:
commit
32c2d912be
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
|
@ -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 () {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue