Skip to content

Commit d2e233c

Browse files
authored
Merge pull request #40 from bigbluebutton/switch-layout
Allow layout changes
2 parents 1586d24 + 2f68e20 commit d2e233c

File tree

11 files changed

+243
-147
lines changed

11 files changed

+243
-147
lines changed

appliance-application/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"graphql": "^16.8.1",
6666
"graphql-ws": "^5.16.0",
6767
"sharp": "^0.33.4",
68+
"uuid": "^11.1.0",
6869
"ws": "^8.14.2"
6970
}
7071
}
Loading
Loading
Loading
Loading
Loading
Loading

appliance-application/packages/main/src/BBBMeeting.ts

Lines changed: 103 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,108 +4,31 @@ import {gql} from '@apollo/client/core';
44
import {BBBGraphQl} from './BBBGraphQl';
55
import type {DisplayManager} from './displayManager';
66
import type {Layout} from '../../common/config';
7-
/*
8-
export async function createBBBMeeting(control: string, screens: Array<string>, displayManager: DisplayManager, leftCallback: () => void) {
7+
import {v7 as uuid} from 'uuid';
98

10-
const bbbGraphQl = new BBBGraphql(control);
11-
const connected = await bbbGraphQl.connect();
12-
if(!connected)
13-
return false;
14-
15-
console.log('connected to graphql');
16-
17-
const joinUrl1 = await bbbGraphQl.getJoinURL({
18-
'sessionName': 'left',
19-
'enforceLayout': 'CAMERAS_ONLY',
20-
'userdata-bbb_hide_actions_bar': false,
21-
'userdata-bbb_display_notifications': false,
22-
'userdata-bbb_auto_share_webcam': true,
23-
'userdata-bbb_listen_only_mode': false,
24-
'userdata-bbb_skip_check_audio': true,
25-
'userdata-bbb_skip_video_preview': true,
26-
'userdata-bbb_preferred_camera_profile': 'high',
27-
'userdata-bbb_hide_nav_bar': true,
28-
'userdata-bbb_auto_join_audio': true,
29-
'userdata-bbb_show_session_details_on_join': false,
30-
});
31-
32-
console.log('joinUrl1', joinUrl1.status);
33-
console.log('joinUrl1', joinUrl1.statusText);
34-
console.log('joinUrl1', JSON.stringify(joinUrl1.data));
35-
console.log('joinUrl1', JSON.stringify(joinUrl1.headers));
36-
console.log('joinUrl1', JSON.stringify(joinUrl1.config));
37-
38-
const joinUrl2 = await bbbGraphQl.getJoinURL({
39-
'sessionName': 'right',
40-
'enforceLayout': 'PRESENTATION_ONLY',
41-
'userdata-bbb_hide_actions_bar': false,
42-
'userdata-bbb_display_notifications': false,
43-
'userdata-bbb_auto_share_webcam': true,
44-
'userdata-bbb_listen_only_mode': false,
45-
'userdata-bbb_skip_check_audio': true,
46-
'userdata-bbb_skip_video_preview': true,
47-
'userdata-bbb_preferred_camera_profile': 'high',
48-
'userdata-bbb_hide_nav_bar': true,
49-
'userdata-bbb_auto_join_audio': true,
50-
'userdata-bbb_show_session_details_on_join': false,
51-
});
52-
53-
console.log('joinUrl2', joinUrl2.status);
54-
console.log('joinUrl2', joinUrl2.statusText);
55-
console.log('joinUrl2', JSON.stringify(joinUrl2.data));
56-
console.log('joinUrl2', JSON.stringify(joinUrl2.headers));
57-
console.log('joinUrl2', JSON.stringify(joinUrl2.config));
58-
59-
return new BBBMeeting(screens, displayManager, leftCallback, bbbGraphQl);
60-
}
61-
*/
62-
export async function createBBBMeeting(control: string, layout: Layout, displayManager: DisplayManager, leftCallback: () => void) {
9+
export async function createBBBMeeting(control: string, displayManager: DisplayManager, leftCallback: () => void) {
6310

6411
const bbbGraphQl = new BBBGraphQl(control);
6512
const connected = await bbbGraphQl.connect(leftCallback);
66-
if(!connected)
67-
return false;
13+
if (!connected) return false;
6814

6915
console.log('connected to graphql');
7016

71-
console.log('layout', layout.label);
72-
73-
const screens: {[key: string]: string} = {};
74-
for (const [key, value] of Object.entries(layout.screens)) {
75-
console.log(key, value);
76-
77-
const joinUrl = await bbbGraphQl.getJoinURL({
78-
sessionName: key,
79-
duplicateSession: false,
80-
...value.bbb_join_parameters,
81-
});
82-
83-
console.log('status', joinUrl.status);
84-
console.log('statusText', joinUrl.statusText);
85-
console.log('data', JSON.stringify(joinUrl.data));
86-
console.log('headers', JSON.stringify(joinUrl.headers));
87-
console.log('config', JSON.stringify(joinUrl.config));
88-
89-
screens[key] = joinUrl.data.response.url;
90-
}
91-
92-
return new BBBMeeting(screens, displayManager, leftCallback, bbbGraphQl);
17+
return new BBBMeeting(displayManager, leftCallback, bbbGraphQl);
9318
}
9419

95-
9620
class BBBMeeting {
97-
private readonly screens: {[key: string]: string};
21+
private screens!: {[key: string]: string};
9822
private displayManager: DisplayManager;
9923

100-
private windows: BrowserWindow[];
101-
private apolloClient: ApolloClient<NormalizedCacheObject>;
24+
private windows: {[key: string]: BrowserWindow};
25+
private apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
10226
private bbbGraphQl: BBBGraphQl;
103-
private mediaScreen: {url: string, window: BrowserWindow};
27+
private mediaScreen: {url: string; window: BrowserWindow} | undefined;
10428

105-
constructor(screens: {[key: string]: string}, displayManager: DisplayManager, leftCallback: () => void, bbbGraphQl: BBBGraphQl) {
106-
this.screens = screens;
29+
constructor(displayManager: DisplayManager, leftCallback: () => void, bbbGraphQl: BBBGraphQl) {
10730
this.displayManager = displayManager;
108-
this.windows = [];
31+
this.windows = {};
10932
this.bbbGraphQl = bbbGraphQl;
11033

11134
this.apolloClient = this.bbbGraphQl.getApolloClient();
@@ -118,7 +41,6 @@ class BBBMeeting {
11841
}
11942

12043
private onUsersLeft(callback: () => void) {
121-
12244
const getMeetingEndData = gql`
12345
subscription getUserCurrent {
12446
user_current {
@@ -133,7 +55,12 @@ class BBBMeeting {
13355
}
13456
}
13557
}
136-
`;
58+
`;
59+
60+
if (this.apolloClient == undefined) {
61+
console.error('Error: apolloClient is undefined');
62+
return;
63+
}
13764

13865
this.apolloClient
13966
.subscribe({
@@ -142,7 +69,7 @@ class BBBMeeting {
14269
.subscribe({
14370
next(data) {
14471
console.log('getMeetingEndData', JSON.stringify(data));
145-
if(data.data.user_current[0].meeting.ended === true){
72+
if (data.data.user_current[0].meeting.ended === true) {
14673
console.log('Meeting ended');
14774
callback();
14875
}
@@ -152,7 +79,6 @@ class BBBMeeting {
15279
},
15380
});
15481

155-
15682
const USER_SESSIONS = gql`
15783
subscription {
15884
user_session(where: {connectionsAlive: {_gt: "0"}}) {
@@ -187,7 +113,35 @@ class BBBMeeting {
187113
});
188114
}
189115

190-
public openScreens() {
116+
public async openScreens(layout: Layout) {
117+
118+
console.log('Switching Layout to: ', layout.label);
119+
120+
// if (this.windows.length > 0) {
121+
// console.log('Windows already exist. Amount of Current windows: ' + this.windows.length);
122+
// for (var window of this.windows) {
123+
// console.log('Closing window: ' + window.id);
124+
// window.close();
125+
// window.destroy();
126+
// }
127+
// }
128+
129+
this.mediaScreen = undefined;
130+
this.screens = {};
131+
132+
for (const [key, value] of Object.entries(layout.screens)) {
133+
console.log("\nProcessing screen: " + key);
134+
console.log("With value: " + value + "\n");
135+
const joinUrl = await this.bbbGraphQl.getJoinURL({
136+
sessionName: key,
137+
duplicateSession: false,
138+
...value.bbb_join_parameters,
139+
});
140+
this.screens[key] = joinUrl.data.response.url;
141+
}
142+
143+
let newWindows: {[key: string]: BrowserWindow} = {};
144+
191145
for (const [screen, url] of Object.entries(this.screens)) {
192146
const screenDisplay = this.displayManager.getDisplay(screen);
193147

@@ -196,40 +150,74 @@ class BBBMeeting {
196150
continue;
197151
}
198152

199-
console.log('screenDisplay', screenDisplay);
200-
201-
const partition = 'persist:windows-' + this.windows.length;
153+
console.log('Processing screen ' + screen);
154+
155+
const partition = 'persist:windows-' + uuid();
156+
157+
// Get old window if exists
158+
let screenWindow = this.windows[screenDisplay.label];
159+
delete this.windows[screenDisplay.label];
160+
161+
if (screenWindow == undefined) {
162+
console.log('Creating new window');
163+
screenWindow = new BrowserWindow({
164+
show: true,
165+
width: screenDisplay.size.width,
166+
height: screenDisplay.size.height,
167+
x: screenDisplay.bounds.x,
168+
y: screenDisplay.bounds.y,
169+
fullscreen: true,
170+
webPreferences: {
171+
partition: partition,
172+
contextIsolation: true,
173+
},
174+
autoHideMenuBar: true,
175+
});
176+
} else {
177+
console.log('Using existing window');
178+
}
202179

203-
const screenWindow = new BrowserWindow({
204-
show: true,
205-
width: screenDisplay.size.width,
206-
height: screenDisplay.size.height,
207-
x: screenDisplay.bounds.x,
208-
y: screenDisplay.bounds.y,
209-
fullscreen: true,
210-
webPreferences: {
211-
partition: partition,
212-
contextIsolation: true,
213-
},
180+
// When leaving the BBB meeting (by visiting another website), BBB will show a confirmation dialog (are you sure blabla)
181+
// This dialog will prevent the loading of a new URL, so we handle this problem here in this event listener
182+
screenWindow.webContents.on('will-prevent-unload', (event) => {
183+
console.log("Prevented unload detected, forcing unload...");
184+
event.preventDefault(); // This stops the confirmation dialog
185+
if(screenWindow) {
186+
console.log(screenDisplay.label + ": Loading URL again: " + url);
187+
screenWindow.loadURL(url);
188+
}
214189
});
215-
//screenWindow.webContents.openDevTools();
190+
console.log('\n' + screenDisplay.label + ': Loading URL: ' + url);
191+
await screenWindow.loadURL(url);
192+
console.log(screenDisplay.label + ': Loading of URL finished.\n');
216193

217-
this.windows.push(screenWindow);
194+
newWindows[screenDisplay.label] = screenWindow;
218195

219-
screenWindow.loadURL(url);
220-
221-
if(url.includes('userdata-bbb_auto_join_audio=true')) {
196+
if (url.includes('userdata-bbb_auto_join_audio=true')) {
222197
this.mediaScreen = {url: url, window: screenWindow};
223198
}
224199
}
200+
201+
// Close all unused windows
202+
Object.values(this.windows).forEach(window => {
203+
console.log('closing window ' + window.id);
204+
window.close();
205+
window.destroy();
206+
});
207+
208+
this.windows = newWindows;
225209
}
226210

227211
public mute() {
228-
this.executeJavaScriptInMediaScreen('document.querySelectorAll(\'button[data-test="muteMicButton"]\')[0].click()').then(r => console.log(r));
212+
this.executeJavaScriptInMediaScreen(
213+
'document.querySelectorAll(\'button[data-test="muteMicButton"]\')[0].click()',
214+
).then(r => console.log(r));
229215
}
230216

231217
public unmute() {
232-
this.executeJavaScriptInMediaScreen('document.querySelectorAll(\'button[data-test="unmuteMicButton"]\')[0].click()').then(r => console.log(r));
218+
this.executeJavaScriptInMediaScreen(
219+
'document.querySelectorAll(\'button[data-test="unmuteMicButton"]\')[0].click()',
220+
).then(r => console.log(r));
233221
}
234222

235223
public async getMediaDevices() {
@@ -253,13 +241,13 @@ class BBBMeeting {
253241
}
254242

255243
private async executeJavaScriptInMediaScreen(command: string) {
256-
if(this.mediaScreen.window) {
244+
if (this.mediaScreen) {
257245
return await this.mediaScreen.window.webContents.executeJavaScript(command);
258246
}
259247
}
260248

261249
private closeScreens() {
262-
this.windows.forEach(window => {
250+
Object.values(this.windows).forEach(window => {
263251
window.close();
264252
window.destroy();
265253
});

appliance-application/packages/main/src/HID.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ export interface HIDActions {
1515
leave: () => void;
1616
mute: () => void;
1717
unmute: () => void;
18+
layout1: () => void;
19+
layout2: () => void;
20+
layout3: () => void;
1821
}

0 commit comments

Comments
 (0)