RTC-5104: screensharing indicator (#536)

* RTC-5104: screensharing indicator

* RTC-5104: screensharing indicator, windows fixes
This commit is contained in:
SergeyS 2019-01-03 13:48:50 +01:00 committed by Vishwas Shashidhar
parent 4832c28dd2
commit 9609bc7976
14 changed files with 363 additions and 7 deletions

View File

@ -83,9 +83,9 @@
<hr>
<p>Get Media Sources:</p>
<button id='get-sources'>Open screen picker</button>
<button id='get-sources'>Open screen picker & screensharing indicator</button>
<br>
<video id='video'></video>
<video id='video' autoPlay loop muted style='max-width: 640px'></video>
<hr>
<p>Get Version Info:</p>
@ -226,8 +226,8 @@
var getSources = document.getElementById('get-sources');
getSources.addEventListener('click', function() {
ssf.getMediaSource({types: ['window', 'screen']}, function(error, source) {
if (error) throw error
navigator.webkitGetUserMedia({
if (error) throw error;
navigator.webkitGetUserMedia({
audio: false,
video: {
mandatory: {
@ -239,12 +239,24 @@
maxHeight: 720
}
}
}, handleStream, handleError)
}, stream => {
handleStream(stream, source.display_id);
}, handleError)
});
});
function handleStream (stream) {
document.querySelector('video').src = URL.createObjectURL(stream)
function handleStream(stream, displayId) {
document.getElementById('video').srcObject = stream;
ssf.showScreenSharingIndicator({
stream,
displayId
}, function(event) {
console.log('screen-sharing-indicator callback', event);
if (event.type === 'stopRequested') {
stream.getVideoTracks().forEach(t => t.stop());
document.getElementById('video').srcObject = null;
}
});
}
function handleError (e) {

View File

@ -25,6 +25,7 @@ const cmds = keyMirror({
setIsInMeeting: null,
setLocale: null,
keyPress: null,
openScreenSharingIndicator: null
});
module.exports = {

View File

@ -17,6 +17,7 @@ const { bringToFront } = require('./bringToFront.js');
const eventEmitter = require('./eventEmitter');
const { isMac } = require('./utils/misc');
const { openScreenPickerWindow } = require('./desktopCapturer');
const { openScreenSharingIndicator } = require('./screenSharingIndicator');
const { setPreloadMemoryInfo, setIsInMeeting, setPreloadWindow } = require('./memoryMonitor');
const apiEnums = require('./enums/api.js');
@ -179,6 +180,11 @@ electron.ipcMain.on(apiName, (event, arg) => {
windowMgr.handleKeyPress(arg.keyCode);
}
break;
case apiCmds.openScreenSharingIndicator:
if (typeof arg.displayId === 'string' && typeof arg.id === 'number') {
openScreenSharingIndicator(event.sender, arg.displayId, arg.id);
}
break;
default:
}

View File

@ -19,6 +19,7 @@ const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const getMediaSources = require('../desktopCapturer/getSources');
const getMediaSource = require('../desktopCapturer/getSource');
const showScreenSharingIndicator = require('../screenSharingIndicator/showScreenSharingIndicator');
const { TitleBar } = require('../windowsTitlebar');
const titleBar = new TitleBar();
const { buildNumber } = require('../../package.json');
@ -317,6 +318,20 @@ function createAPI() {
*/
getMediaSource: getMediaSource,
/**
* Shows a banner that informs user that the screen is being shared.
*
* @param params object with following fields:
* - stream https://developer.mozilla.org/en-US/docs/Web/API/MediaStream/MediaStream object.
* The indicator automatically destroys itself when stream becomes inactive (see MediaStream.active).
* - displayId id of the display that is being shared or that contains the shared app
* @param callback callback function that will be called to handle events.
* Callback receives event object { type: string }. Types:
* - 'error' - error occured. Event object contains 'reason' field.
* - 'stopRequested' - user clicked "Stop Sharing" button.
*/
showScreenSharingIndicator: showScreenSharingIndicator,
/**
* Opens a modal window to configure notification preference.
*/

View File

@ -0,0 +1,104 @@
'use strict';
const electron = require('electron');
const ipcMain = electron.ipcMain;
const path = require('path');
const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
const i18n = require('../translation/i18n');
const { isMac } = require('./../utils/misc.js');
const baseWindowConfig = {
width: 592,
height: 48,
show: false,
modal: true,
frame: false,
focusable: false,
transparent: true,
autoHideMenuBar: true,
resizable: false,
minimizable: false,
maximizable: false,
closable: true,
alwaysOnTop: true,
webPreferences: {
preload: path.join(__dirname, 'renderer.js'),
sandbox: true,
nodeIntegration: false,
devTools: false
}
};
function getTemplatePath() {
let templatePath = path.join(__dirname, 'screen-sharing-indicator.html');
try {
fs.statSync(templatePath).isFile();
} catch (err) {
log.send(logLevels.ERROR, `screen-sharing-indicator: Could not find template ("${templatePath}").`);
}
return `file://${templatePath}`;
}
function openScreenSharingIndicator(eventSender, displayId, id) {
const indicatorScreen = (displayId && electron.screen.getAllDisplays().filter(d => displayId.includes(d.id))[0]) || electron.screen.getPrimaryDisplay();
const screenRect = indicatorScreen.workArea;
const windowConfig = Object.assign({}, baseWindowConfig, {
x: screenRect.x + Math.round((screenRect.width - baseWindowConfig.width) / 2),
y: screenRect.y + screenRect.height - baseWindowConfig.height
});
const indicatorWindow = new electron.BrowserWindow(windowConfig);
indicatorWindow.setVisibleOnAllWorkspaces(true);
indicatorWindow.setMenu(null);
indicatorWindow.loadURL(getTemplatePath());
indicatorWindow.once('ready-to-show', () => {
indicatorWindow.show();
});
indicatorWindow.webContents.on('did-finish-load', () => {
initCrashReporterMain({ process: 'screen sharing indicator window' });
initCrashReporterRenderer(indicatorWindow, { process: 'render | screen sharing indicator window' });
indicatorWindow.webContents.send('window-data', {
id,
i18n: i18n.getMessageFor('ScreenSharingIndicator'),
isMac
});
});
indicatorWindow.webContents.on('crashed', () => {
const errorDialogOptions = {
type: 'error',
title: i18n.getMessageFor('Renderer Process Crashed'),
message: i18n.getMessageFor('Oops! Looks like we have had a crash.'),
buttons: ['Close']
};
electron.dialog.showMessageBox(errorDialogOptions, () => indicatorWindow.close());
});
const handleStopSharingClicked = (event, indicatorId) => {
if (indicatorId === id) {
eventSender.send('stop-sharing-requested', id);
}
}
const handleDestroyScreensharingIndicator = (event, indicatorId) => {
if (indicatorId === id) {
if (!indicatorWindow.isDestroyed()) {
indicatorWindow.close();
}
ipcMain.removeListener('stop-sharing-clicked', handleStopSharingClicked);
ipcMain.removeListener('destroy-screensharing-indicator', handleDestroyScreensharingIndicator);
}
};
ipcMain.on('stop-sharing-clicked', handleStopSharingClicked);
ipcMain.on('destroy-screensharing-indicator', handleDestroyScreensharingIndicator);
}
module.exports = {
openScreenSharingIndicator
};

View File

@ -0,0 +1,38 @@
'use strict';
const { ipcRenderer, crashReporter } = require('electron');
let indicatorId;
function renderDom() {
const stopSharingButton = document.getElementById('stop-sharing-button');
stopSharingButton.addEventListener('click', () => {
ipcRenderer.send('stop-sharing-clicked', indicatorId);
}, false);
const hideButton = document.getElementById('hide-button');
hideButton.addEventListener('click', () => {
window.close();
}, false);
}
ipcRenderer.on('window-data', (event, content) => {
indicatorId = content.id;
const setText = (el, text) => {
document.getElementById(el).innerHTML = (content.i18n[text] || text).replace('Symphony', '<b>Symphony</b>');
};
setText('stop-sharing-button', 'Stop sharing');
setText('hide-button', 'Hide');
setText('text-label', 'You are sharing your screen on Symphony');
document.body.className = content.isMac ? 'mac' : '';
});
ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});
document.addEventListener('DOMContentLoaded', () => {
renderDom();
});

View File

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title data-i18n-text="Screen Sharing"></title>
<style>
@font-face {
font-family: system;
font-style: normal;
src: local(".SFNSText"), local(".HelveticaNeueDeskInterface"), local("Ubuntu Light"), local("Segoe UI"), local("Roboto"), local("Tahoma");
}
body {
font-family: "system";
font-size: 13px;
padding: 0px;
margin: 0px;
user-select: none;
-webkit-app-region: drag;
border: none;
}
.container {
height: 100%;
padding: 10px;
background: rgba(255, 255, 255, 0.94);
border-radius: 4px;
}
body:not(.mac) .container {
border: 1px solid rgb(212, 212, 212);
}
.buttons {
float: right;
font-size: 12px;
font-weight: bold;
}
#stop-sharing-button {
text-transform: uppercase;
background: #107ccc;
color: white;
border: none;
font-size: inherit;
border-radius: 14px;
height: 28px;
padding: 6px 14px;
outline: none;
cursor: pointer;
-webkit-app-region: no-drag;
}
#hide-button {
text-decoration: none;
text-transform: uppercase;
color: #6b717c;
margin-right: 29px;
-webkit-app-region: no-drag;
}
#text-label {
position: relative;
top: -2px;
left: 16px;
font-size: 14px;
}
#drag-area {
display: inline-block;
width: 8px;
height: 22px;
position: relative;
top: 3px;
text-transform: uppercase;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0) 1px,
rgba(0, 0, 0, 0.22) 1px,
rgba(0, 0, 0, 0.22) 2px
);
}
.mac #hide-button {
color: #303237;
}
.mac #stop-sharing-button {
padding: 6px 18px;
}
</style>
</head>
<body>
<div class="container">
<span id='drag-area'></span>
<span id='text-label'>You are sharing your screen on <b>Symphony</b></span>
<span class='buttons'>
<a id='hide-button' href='#'>Hide</a>
<button id='stop-sharing-button'>Stop sharing</button>
</span>
</div>
</body>
</html>

View File

@ -0,0 +1,46 @@
'use strict';
const { ipcRenderer } = require('electron');
const apiEnums = require('../enums/api.js');
const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
let nextIndicatorId = 0;
function showScreenSharingIndicator(options, callback) {
const { stream, displayId } = options;
if (!stream || !stream.active || stream.getVideoTracks().length !== 1) {
callback({type: 'error', reason: 'bad stream'});
return;
}
if (displayId && typeof(displayId) !== 'string') {
callback({type: 'error', reason: 'bad displayId'});
return;
}
const id = ++nextIndicatorId;
ipcRenderer.send(apiName, {
cmd: apiCmds.openScreenSharingIndicator,
displayId: options.displayId,
id
});
const handleStopRequest = (e, indicatorId) => {
if (indicatorId === id) {
callback({type: 'stopRequested'});
}
}
const destroy = () => {
ipcRenderer.send('destroy-screensharing-indicator', id);
options.stream.removeEventListener('inactive', destroy);
ipcRenderer.removeListener('stop-sharing-requested', handleStopRequest);
};
ipcRenderer.on('stop-sharing-requested', handleStopRequest);
options.stream.addEventListener('inactive', destroy);
}
module.exports = showScreenSharingIndicator;

View File

@ -111,6 +111,11 @@
"Select Screen": "Select Screen",
"Share": "Share"
},
"ScreenSharingIndicator": {
"You are sharing your screen on Symphony": "You are sharing your screen on Symphony",
"Stop sharing": "Stop sharing",
"Hide": "Hide"
},
"ScreenSnippet": {
"Done": "Done",
"Erase": "Erase",

View File

@ -109,6 +109,11 @@
"Select Screen": "Select Screen",
"Share": "Share"
},
"ScreenSharingIndicator": {
"You are sharing your screen on Symphony": "You are sharing your screen on Symphony",
"Stop sharing": "Stop sharing",
"Hide": "Hide"
},
"ScreenSnippet": {
"Done": "Done",
"Erase": "Erase",

View File

@ -109,6 +109,11 @@
"Select Screen":"Sélectionnez l'écran",
"Share":"Partager"
},
"ScreenSharingIndicator": {
"You are sharing your screen on Symphony": "Vous partagez votre écran sur Symphony",
"Stop sharing": "Arrêter le partage",
"Hide": "Masquer"
},
"ScreenSnippet":{
"Done":"Terminé",
"Erase":"Effacer",

View File

@ -109,6 +109,11 @@
"Select Screen":"Sélectionnez l'écran",
"Share":"Partager"
},
"ScreenSharingIndicator": {
"You are sharing your screen on Symphony": "Vous partagez votre écran sur Symphony",
"Stop sharing": "Arrêter le partage",
"Hide": "Masquer"
},
"ScreenSnippet":{
"Done":"Terminé",
"Erase":"Effacer",

View File

@ -111,6 +111,11 @@
"Select Screen": "画面を選択",
"Share": "共有"
},
"ScreenSharingIndicator": {
"You are sharing your screen on Symphony": "あなたはSymphony上であなたの画面を共有しています",
"Stop Sharing": "共有を停止",
"Hide": "非表示にする"
},
"ScreenSnippet": {
"Done": "完了",
"Erase": "消去",

View File

@ -109,6 +109,11 @@
"Select Screen": "画面を選択",
"Share": "共有"
},
"ScreenSharingIndicator": {
"You are sharing your screen on Symphony": "あなたはSymphony上であなたの画面を共有しています",
"Stop Sharing": "共有を停止",
"Hide": "非表示にする"
},
"ScreenSnippet": {
"Done": "完了",
"Erase": "消去",