diff --git a/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties
index d86689808c65228fca2ef57074ca37f16fd639bc..52195abd3f82b6e193002f9975d5b1dda2d2ab28 100644
--- a/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties
@@ -1,52 +1,53 @@
-#Mon Mar 02 09:54:03 CET 2009
-rest.doc=Dokumentation
-rest.doc.openapi=Raw JSON
-rest.doc.openapi.title=Dokumentation OpenAPI 3.0
-rest.doc.swagger.ui=Pretty Doku mit SwaggerUI
-rest.title=REST API
-rest.intro=Die REST API macht viele OLAT-Funktionalit\u00E4ten f\u00FCr andere Systeme zug\u00E4nglich. Es ist u.a. m\u00F6glich, Benutzer und Lerngruppen zu verwalten, Kurse zu importieren und Kataloge zu f\u00FChren.
-rest.enabled=REST API Zugang
-rest.on=ein
-warn.beta.feature=Achtung! Diese Funktion befindet sich in einer Versuchsphase. Bitte beachten Sie, dass Fehler auftreten k\u00F6nnen, wenn diese Funktion verwendet wird.
+#Thu Mar 19 21:30:01 CET 2020
 managed.cal=Managed Kalender
-managed.objects=Extern verwaltete Kurse und Gruppen
-managed.intro=Kurse und Gruppen k\u00F6nnen \u00FCber das REST API erstellt werden. Solch extern erstellt Kurse und Gruppen werden als "managed" bezeichnet da ein externes System das datenf\u00FChrende System ist. Die Verwendung dieser Funktion k\u00F6nnen Sie hier ein- und ausschalten.
-managed.group=Managed Gruppen
-managed.repo=Managed Lernressourcen
-managed.relation.role=Managed Benutzer zu Benutzer Beziehungen
-managed.user.portrait=Managed Profilbild
-managed.flags.course.all=Vollst\u00E4ndige externe Verwaltung 
-managed.flags.course.editcontent=Kurseditor
-managed.flags.course.details=Titel und Beschreibung
-managed.flags.course.title=Kurstitel
-managed.flags.course.description=Beschreibung
-managed.flags.course.settings=Einstellungen
 managed.flags.course.access=Einstellung f\u00FCr Zugriff
+managed.flags.course.all=Vollst\u00E4ndige externe Verwaltung 
+managed.flags.course.bookings=Buchungsregeln
+managed.flags.course.calendar=Einstellung f\u00FCr Kalender
 managed.flags.course.chat=Einstellung f\u00FCr Chat
+managed.flags.course.close=Kurs schliessen
+managed.flags.course.copy=Kurs kopieren
+managed.flags.course.delete=Kurs l\u00F6schen
+managed.flags.course.description=Beschreibung
+managed.flags.course.details=Titel und Beschreibung
+managed.flags.course.editcontent=Kurseditor
+managed.flags.course.efficencystatement=Einstellung f\u00FCr Leistungsnachweis
+managed.flags.course.glossary=Einstellung f\u00FCr Glossar 
+managed.flags.course.groups=Gruppenverwaltung
 managed.flags.course.layout=Einstellung f\u00FCr Layout
 managed.flags.course.lecture=Lektionen
-managed.flags.course.lecturemanagement=Lektionen verwalten
 managed.flags.course.lectureconfig=Lektionen und Absenzen in Kurs konfigurieren
+managed.flags.course.lecturemanagement=Lektionen verwalten
+managed.flags.course.membersmanagement=Mitgliederverwaltung
 managed.flags.course.organisations=Organisationen
 managed.flags.course.resourcefolder=Einstellung f\u00FCr Ressourcenordner
-managed.flags.course.efficencystatement=Einstellung f\u00FCr Leistungsnachweis
-managed.flags.course.calendar=Einstellung f\u00FCr Kalender
-managed.flags.course.glossary=Einstellung f\u00FCr Glossar 
-managed.flags.course.bookings=Buchungsregeln
-managed.flags.course.membersmanagement=Mitgliederverwaltung
-managed.flags.course.groups=Gruppenverwaltung
-managed.flags.course.copy=Kurs kopieren
-managed.flags.course.close=Kurs schliessen
-managed.flags.course.delete=Kurs l\u00F6schen
+managed.flags.course.settings=Einstellungen
+managed.flags.course.title=Kurstitel
 managed.flags.group.all=Vollst\u00E4ndige externe Verwaltung
-managed.flags.group.details=Titel, Beschreibung und Einstellungen Pl\u00E4tze/Warteliste
-managed.flags.group.title=Gruppentitel
+managed.flags.group.bookings=Buchungsregeln
+managed.flags.group.delete=Gruppe L\u00F6schen
 managed.flags.group.description=Beschreibung
-managed.flags.group.settings=Einstellungen Pl\u00E4tze/Warteliste
-managed.flags.group.tools=Werkzeuge konfigurieren
-managed.flags.group.members=Mitgliederverwaltung und Sichtbarkeit Mitglieder
+managed.flags.group.details=Titel, Beschreibung und Einstellungen Pl\u00E4tze/Warteliste
 managed.flags.group.display=Sichtbarkeit Mitglieder
+managed.flags.group.members=Mitgliederverwaltung und Sichtbarkeit Mitglieder
 managed.flags.group.membersmanagement=Mitgliederverwaltung
 managed.flags.group.resources=Kurse einbinden
-managed.flags.group.bookings=Buchungsregeln
-managed.flags.group.delete=Gruppe L\u00F6schen
+managed.flags.group.settings=Einstellungen Pl\u00E4tze/Warteliste
+managed.flags.group.title=Gruppentitel
+managed.flags.group.tools=Werkzeuge konfigurieren
+managed.group=Managed Gruppen
+managed.intro=Kurse und Gruppen k\u00F6nnen \u00FCber das REST API erstellt werden. Solch extern erstellt Kurse und Gruppen werden als "managed" bezeichnet da ein externes System das datenf\u00FChrende System ist. Die Verwendung dieser Funktion k\u00F6nnen Sie hier ein- und ausschalten.
+managed.objects=Extern verwaltete Kurse und Gruppen
+managed.relation.role=Managed Benutzer zu Benutzer Beziehungen
+managed.repo=Managed Lernressourcen
+managed.user.portrait=Managed Profilbild
+rest.doc=Dokumentation
+rest.doc.openapi=Raw JSON
+rest.doc.openapi.experimental=Documentation OpenAPI 3.0
+rest.doc.openapi.title=Dokumentation OpenAPI 3.0
+rest.doc.swagger.ui=Pretty Doku mit SwaggerUI
+rest.enabled=REST API Zugang
+rest.intro=Die REST API macht viele OLAT-Funktionalit\u00E4ten f\u00FCr andere Systeme zug\u00E4nglich. Es ist u.a. m\u00F6glich, Benutzer und Lerngruppen zu verwalten, Kurse zu importieren und Kataloge zu f\u00FChren.
+rest.on=ein
+rest.title=REST API
+warn.beta.feature=Achtung\! Diese Funktion befindet sich in einer Versuchsphase. Bitte beachten Sie, dass Fehler auftreten k\u00F6nnen, wenn diese Funktion verwendet wird.
diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java
index d44dc60d21fad04260a1a8fe80194ab6f6349157..7669eee963dc0baf928cb5448ec4464d1da17dcb 100644
--- a/src/main/java/org/olat/collaboration/CollaborationTools.java
+++ b/src/main/java/org/olat/collaboration/CollaborationTools.java
@@ -87,6 +87,8 @@ import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.instantMessaging.ui.ChatToolController;
 import org.olat.modules.adobeconnect.ui.AdobeConnectMeetingDefaultConfiguration;
 import org.olat.modules.adobeconnect.ui.AdobeConnectRunController;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonMeetingDefaultConfiguration;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonRunController;
 import org.olat.modules.co.ContactFormController;
 import org.olat.modules.fo.Forum;
 import org.olat.modules.fo.ForumCallback;
@@ -159,6 +161,7 @@ public class CollaborationTools implements Serializable {
 	public static final String KEY_PORTFOLIO = "portfolioMapKey";
 	public static final String KEY_OPENMEETINGS = "openMeetingsKey";
 	public static final String KEY_ACONNECTMEETINGS = "adobeConnectKey";
+	public static final String KEY_BIGBLUEBUTTON = "bigBlueButtonKey";
 
 	/**
 	 * <code>PROP_CAT_BG_COLLABTOOLS</code> identifies properties concerning
@@ -204,9 +207,13 @@ public class CollaborationTools implements Serializable {
 	 */
 	public static final String TOOL_OPENMEETINGS = "hasOpenMeetings";
 	/**
-	 * constant used to identify the open meetings for a group
+	 * constant used to identify the Adobe Connect for a group
 	 */
 	public static final String TOOL_ADOBECONNECT = "hasAdobeConnect";
+	/**
+	 * constant used to identify the BigBlueButton for a group
+	 */
+	public static final String TOOL_BIGBLUEBUTTON = "hasBigBlueButton";
 	
 	/**
 	 * Only owners have write access to the calendar.
@@ -623,6 +630,11 @@ public class CollaborationTools implements Serializable {
 		AdobeConnectMeetingDefaultConfiguration configuration = new AdobeConnectMeetingDefaultConfiguration(true, true, true);
 		return new AdobeConnectRunController(ureq, wControl, null, null, group, configuration, admin, admin, false);
 	}
+	
+	public Controller createBigBlueButtonController(final UserRequest ureq, WindowControl wControl, final BusinessGroup group, boolean admin) {
+		BigBlueButtonMeetingDefaultConfiguration configuration = new BigBlueButtonMeetingDefaultConfiguration(false);
+		return new BigBlueButtonRunController(ureq, wControl, null, null, group, configuration, admin, admin, false);
+	}
 
 	/**
 	 * @param toolToChange
diff --git a/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java b/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java
index 41b9498840829ee7646e015bd90c32bfbf0468e0..951fbabd5cb20a0d94f3e39e8e5cc45004216760 100644
--- a/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java
+++ b/src/main/java/org/olat/collaboration/CollaborationToolsFactory.java
@@ -39,6 +39,7 @@ import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.group.BusinessGroup;
 import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.modules.adobeconnect.AdobeConnectModule;
+import org.olat.modules.bigbluebutton.BigBlueButtonModule;
 import org.olat.modules.openmeetings.OpenMeetingsModule;
 import org.olat.modules.portfolio.PortfolioV2Module;
 import org.olat.modules.wiki.WikiModule;
@@ -111,6 +112,11 @@ public class CollaborationToolsFactory {
 		if(adobeConnectModule.isEnabled() && adobeConnectModule.isGroupsEnabled()) {
 			toolArr.add(CollaborationTools.TOOL_ADOBECONNECT);
 		}
+
+		BigBlueButtonModule bigBlueButtonModule = CoreSpringFactory.getImpl(BigBlueButtonModule.class);
+		if(bigBlueButtonModule.isEnabled() && bigBlueButtonModule.isGroupsEnabled()) {
+			toolArr.add(CollaborationTools.TOOL_BIGBLUEBUTTON);
+		}
 		TOOLS = ArrayHelper.toArray(toolArr);				
 	}
 	
@@ -144,21 +150,20 @@ public class CollaborationToolsFactory {
 		if (ores == null) throw new AssertException("Null is not allowed here, you have to provide an existing ores here!");
 		
 		final String cacheKey = Long.toString(ores.getResourceableId().longValue());
-		boolean debug = log.isDebugEnabled();
 		//sync operation cluster wide
 
 		CollaborationTools collabTools = cache.get(cacheKey);
 		if (collabTools != null) {		
-			if (debug) log .debug("loading collabTool from cache. Ores: " + ores.getResourceableId());		
+			log.debug("loading collabTool from cache. Ores: {}", ores.getResourceableId());		
 			if (collabTools.isDirty()) {
-				if (debug) log .debug("CollabTools were in cache but dirty. Creating new ones. Ores: " + ores.getResourceableId());
+				log.debug("CollabTools were in cache but dirty. Creating new ones. Ores: {}", ores.getResourceableId());
 				CollaborationTools tools = new CollaborationTools(coordinatorManager, ores);
 				//update forces clusterwide invalidation of this object
 				cache.update(cacheKey, tools);
 				collabTools = tools;
 			}	
 		} else {
-			if (debug) log .debug("collabTool not in cache. Creating new ones. Ores: " + ores.getResourceableId());
+			log.debug("collabTool not in cache. Creating new ones. Ores: {}", ores.getResourceableId());
 	
 			CollaborationTools tools = new CollaborationTools(coordinatorManager, ores);
 			CollaborationTools cachedTools = cache.putIfAbsent(cacheKey, tools);
diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties
index fbdd32ab0629c30b7cfee9ab6e3de25b7e598952..433cd6d7fe6b04a685988fa05fc386f648fb666f 100644
--- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties
@@ -4,6 +4,7 @@ calendar.access.all=Alle Mitglieder
 calendar.access.owners=Besitzer bzw. Betreuer
 calendar.access.title=Kalender Schreibberechtigung konfigurieren
 collabtools.named.hasAdobeConnect=Adobe Connect
+collabtools.named.hasBigBlueButton=BigBlueButton
 collabtools.named.hasCalendar=Kalender
 collabtools.named.hasChat=Chat
 collabtools.named.hasContactForm=E-Mail
diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties
index 98095422dd12a084de2858b16228d4a4e4edc7a3..8a5df549994bb9b9ae2e57da8f4967cc3aacf182 100644
--- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties
@@ -4,6 +4,7 @@ calendar.access.all=All members
 calendar.access.owners=Coaches
 calendar.access.title=Configure calendar write permission
 collabtools.named.hasAdobeConnect=Adobe Connect
+collabtools.named.hasBigBlueButton=BigBlueButton
 collabtools.named.hasCalendar=Calendar
 collabtools.named.hasChat=Chat
 collabtools.named.hasContactForm=E-mail
diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_fr.properties
index 7975e9b72c1daf59ecc2b767d1be012ee9b7a61f..45facd79d7a009c2e8bbae9ef0e341f08d0fbd65 100644
--- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_fr.properties
@@ -4,6 +4,7 @@ calendar.access.all=Tous les membres
 calendar.access.owners=Propri\u00E9taire resp. tuteur
 calendar.access.title=Configurer droit d'\u00E9criture calendrier
 collabtools.named.hasAdobeConnect=Adobe Connect
+collabtools.named.hasBigBlueButton=BigBlueButton
 collabtools.named.hasCalendar=Calendrier
 collabtools.named.hasChat=Chat
 collabtools.named.hasContactForm=E-mail
diff --git a/src/main/java/org/olat/core/logging/activity/OlatResourceableType.java b/src/main/java/org/olat/core/logging/activity/OlatResourceableType.java
index 44ca3130ca6b01785f623114207b437ff5045e28..103d7a6802f07cf815a815ce25bff5d3af877edb 100644
--- a/src/main/java/org/olat/core/logging/activity/OlatResourceableType.java
+++ b/src/main/java/org/olat/core/logging/activity/OlatResourceableType.java
@@ -93,6 +93,7 @@ public enum OlatResourceableType implements ILoggingResourceableType {
 	/** represents virtual class room **/
 	openmeetings,
 	adobeconnect,
+	bigbluebutton,
 
 	/** business path component **/
 	businessPath,
diff --git a/src/main/java/org/olat/course/_spring/courseContext.xml b/src/main/java/org/olat/course/_spring/courseContext.xml
index dbfa5f111c4d70e34157d9ef63ccc29454773651..ee189a56ec4997b677933c93e3f25fb9e1099035 100644
--- a/src/main/java/org/olat/course/_spring/courseContext.xml
+++ b/src/main/java/org/olat/course/_spring/courseContext.xml
@@ -22,6 +22,7 @@
 	<import resource="classpath:/org/olat/course/nodes/gotomeeting/_spring/buildingblockContext.xml"/>
 	<import resource="classpath:/org/olat/course/nodes/openmeetings/_spring/buildingblockContext.xml"/>
 	<import resource="classpath:/org/olat/course/nodes/adobeconnect/_spring/buildingblockContext.xml"/>
+	<import resource="classpath:/org/olat/course/nodes/bigbluebutton/_spring/buildingblockContext.xml"/>
 	<import resource="classpath:/org/olat/course/nodes/portfolio/_spring/portfolioBBContext.xml"/>
 	<import resource="classpath:/org/olat/course/nodes/vitero/_spring/buildingblockContext.xml"/>
 	<import resource="classpath:/org/olat/course/statistic/_spring/statisticContext.xml"/>
diff --git a/src/main/java/org/olat/course/nodes/BigBlueButtonCourseNode.java b/src/main/java/org/olat/course/nodes/BigBlueButtonCourseNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..556814c9963a58dddc379955fb26b79d07058d48
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/BigBlueButtonCourseNode.java
@@ -0,0 +1,153 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes;
+
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.messages.MessageUIFactory;
+import org.olat.core.gui.control.generic.tabbable.TabbableController;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.id.Roles;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.Util;
+import org.olat.course.ICourse;
+import org.olat.course.condition.ConditionEditController;
+import org.olat.course.editor.CourseEditorEnv;
+import org.olat.course.editor.NodeEditController;
+import org.olat.course.editor.StatusDescription;
+import org.olat.course.groupsandrights.CourseGroupManager;
+import org.olat.course.nodes.bigbluebutton.BigBlueButtonEditController;
+import org.olat.course.run.navigation.NodeRunConstructionResult;
+import org.olat.course.run.userview.CourseNodeSecurityCallback;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.ModuleConfiguration;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonMeetingDefaultConfiguration;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonRunController;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonCourseNode extends AbstractAccessableCourseNode {
+	
+	private static final long serialVersionUID = 7965344505304490859L;
+	private static final Logger log = Tracing.createLoggerFor(BigBlueButtonCourseNode.class);
+	public static final String TYPE = "bigbluebutton";
+
+	// configuration
+	public static final String CONF_VC_CONFIGURATION = "vc_configuration";
+
+	private transient CourseGroupManager groupMgr;
+	
+	public BigBlueButtonCourseNode() {
+		this(null);
+	}
+	
+	public BigBlueButtonCourseNode(CourseNode parent) {
+		super(TYPE, parent);
+	}
+	
+	@Override
+	protected String getDefaultTitleOption() {
+		// default is to only display content because the room has its own room title
+		return CourseNode.DISPLAY_OPTS_CONTENT;
+	}
+
+	@Override
+	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel,
+			ICourse course, UserCourseEnvironment userCourseEnv) {
+		CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(userCourseEnv.getCourseEditorEnv().getCurrentCourseNodeId());
+		// create edit controller
+		BigBlueButtonEditController childTabCtrl = new BigBlueButtonEditController(ureq, wControl, this, course, userCourseEnv);
+		
+		NodeEditController nodeEditCtr = new NodeEditController(ureq, wControl, course, chosenNode,
+				userCourseEnv, childTabCtrl);
+		nodeEditCtr.addControllerListener(childTabCtrl);
+		return nodeEditCtr;
+	}
+
+	@Override
+	public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl,
+			UserCourseEnvironment userCourseEnv, CourseNodeSecurityCallback nodeSecCallback, String nodecmd) {
+
+		Controller controller;
+		Roles roles = ureq.getUserSession().getRoles();
+		if(roles.isGuestOnly()) {
+			Translator trans = Util.createPackageTranslator(AdobeConnectCourseNode.class, ureq.getLocale());
+			String title = trans.translate("guestnoaccess.title");
+			String message = trans.translate("guestnoaccess.message");
+			controller = MessageUIFactory.createInfoMessage(ureq, wControl, title, message);
+		} else {
+			// check if user is moderator of the virtual classroom
+			boolean admin = userCourseEnv.isAdmin();
+			boolean moderator = admin || userCourseEnv.isCoach();
+			// create run controller
+			RepositoryEntry entry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+
+			ModuleConfiguration config = getModuleConfiguration();
+			boolean moderatorStart = config.getBooleanSafe(BigBlueButtonEditController.MODERATOR_START_MEETING, true);
+			BigBlueButtonMeetingDefaultConfiguration configuration = new BigBlueButtonMeetingDefaultConfiguration(moderatorStart);
+			controller = new BigBlueButtonRunController(ureq, wControl, entry, getIdent(), null, configuration,
+					admin, moderator, userCourseEnv.isCourseReadOnly());
+		}
+		Controller ctrl = TitledWrapperHelper.getWrapper(ureq, wControl, controller, this, "o_vc_icon");
+		return new NodeRunConstructionResult(ctrl);
+	}
+	
+	@Override
+	public StatusDescription isConfigValid() {
+		if (oneClickStatusCache != null) { return oneClickStatusCache[0]; }
+		
+		StatusDescription sd = StatusDescription.NOERROR;
+		if(groupMgr != null) {
+			//
+		}
+		return sd;
+	}
+
+	@Override
+	public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
+		String translatorStr = Util.getPackageName(ConditionEditController.class);
+		if (groupMgr == null) {
+			groupMgr = cev.getCourseGroupManager();
+		}
+		List<StatusDescription> statusDescs = isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions());
+		return StatusDescriptionHelper.sort(statusDescs);
+	}
+	
+	@Override
+	public RepositoryEntry getReferencedRepositoryEntry() {
+		return null;
+	}
+
+	@Override
+	public boolean needsReferenceToARepositoryEntry() {
+		return false;
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties
index bf39424438bd1854d96ed318e425fc2d27b5732b..2dcb0699fdc0c418d0b4bcb07ecb32f563b14ac4 100644
--- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties
@@ -6,6 +6,7 @@ guestnoaccess.title=Kein Zugang f\u00FCr G\u00E4ste
 learningObjectives.title=Beschreibung
 preview.notavailable=F\u00FCr diesen Kursbaustein existiert keine Vorschau.
 title_bc=Ordner
+title_bigbluebutton=Big Blue Button
 title_cl=Checkliste (alt)
 title_checklist=Checkliste
 title_co=E-Mail
diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties
index b4ce9977c361e66060a5fab25795fbb017648525..02b52346a7506b95ec0fe7944804e3ac4e2fcfb1 100644
--- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties
@@ -1,4 +1,5 @@
-#Wed Jan 11 13:33:53 CET 2017
+#Thu Mar 19 21:29:14 CET 2020
+assessment.review.explanation=The assessment of your performance has not yet been completed by your coach. Once the assessment is released, it will be displayed here.
 editor.lock.message=This course element is being modified and therefore locked.
 editor.lock.title=Course element locked
 freezenoaccess.message=This course is in read-only mode.
@@ -9,6 +10,7 @@ learningObjectives.title=Description
 personal.title=Performance summary
 preview.notavailable=No preview available for this course element
 title_bc=Folder
+title_bigbluebutton=Big Blue Button
 title_blog=Blog
 title_checklist=Check list
 title_cl=Check list (old)
@@ -25,15 +27,14 @@ title_iqtest=Test
 title_ita=Task
 title_ll=Link list
 title_ms=Assessment
+title_pf=Participant Folder
 title_podcast=Podcast
 title_projectbroker=Topic assignment
 title_qti21assessment=Test (QTI 2.1)
 title_scorm=SCORM learning content
-title_pf=Participant Folder
 title_sp=Single page
 title_st=Structure
 title_ta=<s>Task (deprecated)</s>
 title_tu=External page
 title_video=Video
 title_wiki=Wiki
-assessment.review.explanation=The assessment of your performance has not yet been completed by your coach. Once the assessment is released, it will be displayed here.
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonAssessmentHandler.java b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonAssessmentHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d461be43255f0c7c7f80e3a18100b677e1cf26a
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonAssessmentHandler.java
@@ -0,0 +1,40 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.bigbluebutton;
+
+import org.olat.course.learningpath.LearningPathOnlyAssessmentHandler;
+import org.olat.course.nodes.BigBlueButtonCourseNode;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 20 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonAssessmentHandler extends LearningPathOnlyAssessmentHandler {
+
+	@Override
+	public String acceptCourseNodeType() {
+		return BigBlueButtonCourseNode.TYPE;
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonConfigForm.java b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonConfigForm.java
new file mode 100644
index 0000000000000000000000000000000000000000..2dadf712c0537ade225386ed7a2e708cea550829
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonConfigForm.java
@@ -0,0 +1,82 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.bigbluebutton;
+
+import java.util.Collection;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.editor.NodeEditController;
+import org.olat.modules.ModuleConfiguration;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonConfigForm extends FormBasicController {
+	
+	private static final String[] accessKeys = new String[] { "start" };
+	
+	private MultipleSelectionElement accessEl;
+	
+	private final ModuleConfiguration config;
+
+	public BigBlueButtonConfigForm(UserRequest ureq, WindowControl wControl, ModuleConfiguration config) {
+		super(ureq, wControl);
+		this.config = config;
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String[] accessValues = new String[] { translate("vc.access.start") };
+		
+		boolean moderatorStart = config.getBooleanSafe(BigBlueButtonEditController.MODERATOR_START_MEETING, true);
+
+		accessEl = uifactory.addCheckboxesVertical("vc.access.label", "vc.access.label", formLayout, accessKeys, accessValues, 1);
+		accessEl.select(accessKeys[0], moderatorStart);
+
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("save", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		Collection<String> selectedKeys = accessEl.getSelectedKeys();
+		config.setBooleanEntry(BigBlueButtonEditController.ACCESS_BY_DATES, selectedKeys.contains(accessKeys[0]));
+		config.setBooleanEntry(BigBlueButtonEditController.GUEST_ACCESS_ALLOWED, !selectedKeys.contains(accessKeys[1]));
+		fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonCourseNodeConfiguration.java b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonCourseNodeConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c98486711632fc6fad5e96f17cd17bc6d11514d
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonCourseNodeConfiguration.java
@@ -0,0 +1,87 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * 12.10.2011 by frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.bigbluebutton;
+
+import java.util.Locale;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.util.Util;
+import org.olat.core.util.nodes.INode;
+import org.olat.course.nodes.AbstractCourseNodeConfiguration;
+import org.olat.course.nodes.BigBlueButtonCourseNode;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.nodes.CourseNodeConfiguration;
+import org.olat.course.nodes.CourseNodeGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonModule;
+
+/**
+ * Description:<br>
+ * Configuration for the Big Blue Button course element.
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonCourseNodeConfiguration extends AbstractCourseNodeConfiguration {
+	
+	private final String alias;
+	
+	public BigBlueButtonCourseNodeConfiguration() {
+		this("bigbluebutton");
+	}
+	
+	public BigBlueButtonCourseNodeConfiguration(String alias) {
+		this.alias = alias;
+	}
+
+	@Override
+	public String getAlias() {
+		return alias;
+	}
+	
+	@Override
+	public String getGroup() {
+		return CourseNodeGroup.collaboration.name();
+	}
+
+	@Override
+	public String getIconCSSClass() {
+		return "o_vc_icon";
+	}
+
+	@Override
+	public CourseNode getInstance(INode parent) {
+		return new BigBlueButtonCourseNode();
+	}
+
+	@Override
+	public String getLinkText(Locale locale) {
+		Translator fallback = Util.createPackageTranslator(CourseNodeConfiguration.class, locale);
+		Translator translator = Util.createPackageTranslator(this.getClass(), locale, fallback);
+		return translator.translate("title_vc");
+	}
+	
+	@Override
+	public boolean isEnabled() {
+		BigBlueButtonModule module = CoreSpringFactory.getImpl(BigBlueButtonModule.class);
+		return module.isEnabled() && module.isCoursesEnabled();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonEditController.java b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonEditController.java
new file mode 100644
index 0000000000000000000000000000000000000000..060ddd44c2c6695e85b223d283403c57abdcce09
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonEditController.java
@@ -0,0 +1,129 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.bigbluebutton;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.tabbedpane.TabbedPane;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.condition.Condition;
+import org.olat.course.condition.ConditionEditController;
+import org.olat.course.editor.NodeEditController;
+import org.olat.course.nodes.BigBlueButtonCourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.ModuleConfiguration;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonEditController extends ActivateableTabbableDefaultController {
+
+	public static final String ACCESS_BY_DATES = "accessByDates";
+	public static final String GUEST_ACCESS_ALLOWED = "guestAccessAllowed";
+	public static final String MODERATOR_START_MEETING = "moderatorStartMeeting";
+	
+	private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility";
+	public static final String PANE_TAB_VCCONFIG = "pane.tab.vcconfig";
+	private static final String[] paneKeys = { PANE_TAB_VCCONFIG, PANE_TAB_ACCESSIBILITY };
+	
+	private TabbedPane tabPane;
+	private final VelocityContainer myContent;
+
+	private BigBlueButtonConfigForm configCtrl;
+	private ConditionEditController accessibilityCondContr;
+	
+	private final ModuleConfiguration config;
+	private final BigBlueButtonCourseNode courseNode;
+	
+	public BigBlueButtonEditController(UserRequest ureq, WindowControl wControl, BigBlueButtonCourseNode courseNode,
+			ICourse course, UserCourseEnvironment userCourseEnv) {
+		super(ureq, wControl);
+		this.courseNode = courseNode;
+		config = courseNode.getModuleConfiguration();	
+		
+		String providerId = config.getStringValue("vc_provider_id");
+		if("wimba".equals(providerId)) {
+			showWarning("wimba.not.supported.message");
+		}
+		
+		Condition accessCondition = courseNode.getPreConditionAccess();
+		accessibilityCondContr = new ConditionEditController(ureq, wControl, userCourseEnv,
+				accessCondition, AssessmentHelper.getAssessableNodes(course.getEditorTreeModel(), courseNode));
+		listenTo(accessibilityCondContr);
+		
+		myContent = createVelocityContainer("edit");
+		
+		configCtrl = new BigBlueButtonConfigForm(ureq, getWindowControl(), config);
+		listenTo(configCtrl);
+		myContent.put("configuration", configCtrl.getInitialComponent());
+	}
+	
+	@Override
+	public String[] getPaneKeys() {
+		return paneKeys;
+	}
+
+	@Override
+	public TabbedPane getTabbedPane() {
+		return tabPane;
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		//
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if (source == accessibilityCondContr) {
+			if (event == Event.CHANGED_EVENT) {
+				Condition cond = accessibilityCondContr.getCondition();
+				courseNode.setPreConditionAccess(cond);
+				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+			}
+		} else if (source == configCtrl) {
+			if (event == Event.CANCELLED_EVENT) {
+				// do nothing
+			} else if (event == Event.DONE_EVENT || event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
+				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+			}
+		}
+	}
+	
+	@Override
+	public void addTabs(TabbedPane tabbedPane) {
+		tabPane = tabbedPane;
+		tabbedPane.addTab(translate(PANE_TAB_VCCONFIG), myContent);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonLearningPathNodeHandler.java b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonLearningPathNodeHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..80a37edc30bf98649af496b8b2a089488b308c32
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonLearningPathNodeHandler.java
@@ -0,0 +1,80 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.bigbluebutton;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.learningpath.LearningPathConfigs;
+import org.olat.course.learningpath.LearningPathEditConfigs;
+import org.olat.course.learningpath.LearningPathNodeHandler;
+import org.olat.course.learningpath.model.ModuleLearningPathConfigs;
+import org.olat.course.learningpath.ui.LearningPathNodeConfigController;
+import org.olat.course.nodes.BigBlueButtonCourseNode;
+import org.olat.course.nodes.CourseNode;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 20 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonLearningPathNodeHandler implements LearningPathNodeHandler {
+
+	private static final LearningPathEditConfigs EDIT_CONFIGS = LearningPathEditConfigs.builder()
+			.enableNodeVisited()
+			.enableConfirmed()
+			.build();
+	
+	@Override
+	public String acceptCourseNodeType() {
+		return BigBlueButtonCourseNode.TYPE;
+	}
+
+	@Override
+	public boolean isSupported() {
+		return true;
+	}
+
+	@Override
+	public LearningPathConfigs getConfigs(CourseNode courseNode) {
+		return new ModuleLearningPathConfigs(courseNode.getModuleConfiguration(), true);
+	}
+
+	@Override
+	public Controller createConfigEditController(UserRequest ureq, WindowControl wControl, RepositoryEntry courseEntry,
+			CourseNode courseNode) {
+		return new LearningPathNodeConfigController(ureq, wControl, courseEntry, courseNode, EDIT_CONFIGS);
+	}
+
+	@Override
+	public LearningPathEditConfigs getEditConfigs() {
+		return EDIT_CONFIGS;
+	}
+
+	@Override
+	public void onMigrated(CourseNode courseNode) {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonPeekViewController.java b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonPeekViewController.java
new file mode 100644
index 0000000000000000000000000000000000000000..310ab4e0083eae3e248d9f817bf9ec4025923093
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/BigBlueButtonPeekViewController.java
@@ -0,0 +1,59 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.bigbluebutton;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonPeekViewController extends FormBasicController {
+
+	public BigBlueButtonPeekViewController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	
+}
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/_content/edit.html b/src/main/java/org/olat/course/nodes/bigbluebutton/_content/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..f29500cf3610619203240ec965831da5e90b74a9
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/_content/edit.html
@@ -0,0 +1 @@
+$r.render("configuration")
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/bigbluebutton/_i18n/LocalStrings_de.properties
new file mode 100644
index 0000000000000000000000000000000000000000..203904f29582e5dad3feacea05b25d07a2d0ba8b
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/_i18n/LocalStrings_de.properties
@@ -0,0 +1,11 @@
+#Thu Mar 19 21:29:52 CET 2020
+condition.accessibility.title=Zugang
+guest.allowed=Zutritt
+moderator.start.meeting=Meeting \u00F6ffnen
+pane.tab.accessibility=Zugang
+pane.tab.vcconfig=Konfiguration
+title_vc=Big Blue Button
+vc.access.dates=Virtuelles Klassenzimmer soll nur zu bestimmten Terminen betreten werden k\u00F6nnen
+vc.access.label=Zugang
+vc.access.start=Nur Moderatoren d\u00FCrfen diesen Raum er\u00F6ffnen
+vc.options.label=Zutrittsberechtigung
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/bigbluebutton/_i18n/LocalStrings_en.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1528253b111938925af6050008a9fe01b13a83b8
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/_i18n/LocalStrings_en.properties
@@ -0,0 +1,11 @@
+#Thu Mar 19 21:29:18 CET 2020
+condition.accessibility.title=Access
+guest.allowed=Access
+moderator.start.meeting=Room opening
+pane.tab.accessibility=Access
+pane.tab.vcconfig=Configuration
+title_vc=Big Blue Button
+vc.access.dates=Virtual classroom shall only be available at defined dates
+vc.access.label=Access authorisation
+vc.access.start=Only moderators are allowed to open this virtual classroom
+vc.options.label=Access authorisation
diff --git a/src/main/java/org/olat/course/nodes/bigbluebutton/_spring/buildingblockContext.xml b/src/main/java/org/olat/course/nodes/bigbluebutton/_spring/buildingblockContext.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4ca38847d8272c5de7d03f4bd9f4bcc90897580f
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/bigbluebutton/_spring/buildingblockContext.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:context="http://www.springframework.org/schema/context" 
+	xsi:schemaLocation="
+  http://www.springframework.org/schema/beans 
+  http://www.springframework.org/schema/beans/spring-beans.xsd 
+  http://www.springframework.org/schema/context 
+  http://www.springframework.org/schema/context/spring-context.xsd">
+
+	<bean id="bigbluebutton" class="org.olat.course.nodes.bigbluebutton.BigBlueButtonCourseNodeConfiguration" scope="prototype">
+		<property name="order" value="307" />
+	</bean>
+	
+</beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java
index 30421204cabf5346599ea105acd4ed773909236a..bdc0fdfb47fd6eaa15baa07427d13208c359bb1d 100644
--- a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java
+++ b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java
@@ -85,6 +85,7 @@ import org.olat.instantMessaging.CloseInstantMessagingEvent;
 import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.instantMessaging.InstantMessagingService;
 import org.olat.modules.adobeconnect.AdobeConnectModule;
+import org.olat.modules.bigbluebutton.BigBlueButtonModule;
 import org.olat.modules.co.ContactFormController;
 import org.olat.modules.openmeetings.OpenMeetingsModule;
 import org.olat.modules.portfolio.PortfolioV2Module;
@@ -131,6 +132,7 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 	public static final OLATResourceable ORES_TOOLBOOKING = OresHelper.createOLATResourceableType("toolbooking");
 	public static final OLATResourceable ORES_TOOLOPENMEETINGS = OresHelper.createOLATResourceableType("toolopenmeetings");
 	public static final OLATResourceable ORES_TOOLADOBECONNECT = OresHelper.createOLATResourceableType("tooladobeconnect");
+	public static final OLATResourceable ORES_TOOLBIGBLUEBUTTON = OresHelper.createOLATResourceableType("toolbigbluebutton");
 	public static final OLATResourceable ORES_TOOLWIKI = OresHelper.createOLATResourceableType(WikiManager.WIKI_RESOURCE_FOLDER_NAME);
 
 	// activity identifiers are used as menu user objects and for the user
@@ -161,8 +163,10 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 	public static final String ACTIVITY_MENUSELECT_PORTFOLIO = "MENU_SHOW_PORTFOLIO";
 	/* activity identifier: user selected show OPENMEETINGS in menu */
 	public static final String ACTIVITY_MENUSELECT_OPENMEETINGS = "MENU_SHOW_OPENMEETINGS";
-	/* activity identifier: user selected show OPENMEETINGS in menu */
+	/* activity identifier: user selected show Adobe Connect in menu */
 	public static final String ACTIVITY_MENUSELECT_ADOBECONNECT = "MENU_SHOW_ADOBECONNECT";
+	/* activity identifier: user selected show BigBlueButton in menu */
+	public static final String ACTIVITY_MENUSELECT_BIGBLUEBUTTON = "MENU_SHOW_BIGBLUEBUTTON";
 	/* activity identifier: user selected show access control in menu */
 	/* access control of resources */
 	public static final String ACTIVITY_MENUSELECT_AC = "MENU_SHOW_AC";
@@ -208,6 +212,7 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 	private GenericTreeNode nodePortfolio;
 	private GenericTreeNode nodeOpenMeetings;
 	private GenericTreeNode nodeAdobeConnect;
+	private GenericTreeNode nodeBigBlueButton;
 	private GenericTreeNode nodeContact, nodeGroupOwners, nodeResources, nodeInformation, nodeAdmin;
 	private boolean groupRunDisabled;
 	private OLATResourceable assessmentEventOres;
@@ -232,6 +237,8 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 	@Autowired
 	private AdobeConnectModule adobeConnectModule;
 	@Autowired
+	private BigBlueButtonModule bigBlueButtonModule;
+	@Autowired
 	private BusinessGroupService businessGroupService;	
 
 	/**
@@ -656,6 +663,8 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 			doOpenMeetings(ureq);
 		} else if (ACTIVITY_MENUSELECT_ADOBECONNECT.equals(cmd)) {
 			doAdobeConnect(ureq);
+		} else if(ACTIVITY_MENUSELECT_BIGBLUEBUTTON.equals(cmd)) {
+			doBigBlueButton(ureq);
 		} else if (ACTIVITY_MENUSELECT_AC.equals(cmd)) {
 			doAccessControlHistory(ureq);
 		} 
@@ -810,6 +819,20 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 		listenTo(collabToolCtr);
 		mainPanel.setContent(collabToolCtr.getInitialComponent());
 	}
+	
+	private void doBigBlueButton(UserRequest ureq) {
+		addLoggingResourceable(LoggingResourceable.wrap(ORES_TOOLBIGBLUEBUTTON, OlatResourceableType.bigbluebutton));
+		
+		ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(ORES_TOOLBIGBLUEBUTTON);
+		WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, getWindowControl());
+		ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapPortfolioOres(ce.getOLATResourceable()));
+		addToHistory(ureq, bwControl);
+		
+		CollaborationTools collabTools = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup);
+		collabToolCtr = collabTools.createBigBlueButtonController(ureq, bwControl, businessGroup, isAdmin);
+		listenTo(collabToolCtr);
+		mainPanel.setContent(collabToolCtr.getInitialComponent());
+	}
 
 	private Activateable2 doAdministration(UserRequest ureq) {
 		removeAsListenerAndDispose(bgEditCntrllr);
@@ -972,6 +995,16 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 				listenTo(mc); // cleanup on dispose
 				mainPanel.setContent(mc.getInitialComponent());
 			}
+		} else if (OresHelper.equals(ores, ORES_TOOLBIGBLUEBUTTON)) {
+			if (nodeBigBlueButton != null) {
+				doBigBlueButton(ureq);
+				bgTree.setSelectedNode(nodeBigBlueButton);
+			} else if(mainPanel != null) { // not enabled
+				String text = translate("warn.portfolionotavailable");
+				Controller mc = MessageUIFactory.createInfoMessage(ureq, getWindowControl(), null, text);
+				listenTo(mc); // cleanup on dispose
+				mainPanel.setContent(mc.getInitialComponent());
+			}
 		} else if (OresHelper.equals(ores, ORES_TOOLADMIN)) {
 			if (nodeAdmin != null) {
 				doAdministration(ureq).activate(ureq, entries, ce.getTransientState());
@@ -1230,7 +1263,18 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 			root.addChild(gtnChild);
 			nodeAdobeConnect = gtnChild;
 		}
-
+		
+		if(bigBlueButtonModule.isEnabled() && bigBlueButtonModule.isGroupsEnabled()
+				&& collabTools.isToolEnabled(CollaborationTools.TOOL_BIGBLUEBUTTON)) {
+			gtnChild = new GenericTreeNode(nodeIdPrefix.concat("bigbluebutton"));
+			gtnChild.setTitle(translate("menutree.bigbluebutton"));
+			gtnChild.setUserObject(ACTIVITY_MENUSELECT_BIGBLUEBUTTON);
+			gtnChild.setAltText(translate("menutree.bigbluebutton.alt"));
+			gtnChild.setIconCssClass("o_vc_icon");
+			root.addChild(gtnChild);
+			nodeBigBlueButton = gtnChild;
+		}
+	
 		if (isAdmin) {
 			gtnChild = new GenericTreeNode(nodeIdPrefix.concat("admin"));
 			gtnChild.setTitle(translate("menutree.administration"));
diff --git a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties
index e47614ca0f6ebb23209cc8bd98cf3b901c9b1063..6db29859769831a792c5feff62b64993da31460f 100644
--- a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties
@@ -19,6 +19,8 @@ menutree.administration=Administration
 menutree.administration.alt=Administration
 menutree.adobeconnect=Adobe Connect
 menutree.adobeconnect.alt=Adobe Connect web conferencing
+menutree.bigbluebutton=BigBlueButton
+menutree.bigbluebutton.alt=BigBlueButton Web Conferencing
 menutree.calendar=Kalender
 menutree.calendar.alt=Kalender
 menutree.chat=Chat
diff --git a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties
index 98f9998d92eb25cf3f89e60978cd9e0f2013f4de..7acc19a219daea7b1cb3b5b713be26e1529e71c6 100644
--- a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties
@@ -21,6 +21,8 @@ menutree.administration=Administration
 menutree.administration.alt=Administration
 menutree.adobeconnect=Adobe Connect
 menutree.adobeconnect.alt=Adobe Connect web conferencing
+menutree.bigbluebutton=BigBlueButton
+menutree.bigbluebutton.alt=BigBlueButton Web Conferencing
 menutree.calendar=Calendar
 menutree.calendar.alt=Calendar
 menutree.chat=Chat
diff --git a/src/main/java/org/olat/modules/_spring/modulesContext.xml b/src/main/java/org/olat/modules/_spring/modulesContext.xml
index 3604de81012d25d772de184d6226b61f555ee44b..6b349e650bfd175c6b61c82c76c57c40f2aef4a7 100644
--- a/src/main/java/org/olat/modules/_spring/modulesContext.xml
+++ b/src/main/java/org/olat/modules/_spring/modulesContext.xml
@@ -11,6 +11,7 @@
 	<context:component-scan base-package="org.olat.modules"/>
 
 	<import resource="classpath:/org/olat/modules/adobeconnect/_spring/adobeConnectContext.xml"/>
+	<import resource="classpath:/org/olat/modules/bigbluebutton/_spring/bigBlueButtonContext.xml"/>
 	<import resource="classpath:/org/olat/modules/coach/_spring/coachContext.xml"/>
 	<import resource="classpath:/org/olat/modules/iq/_spring/iqContext.xml"/>
 	<import resource="classpath:/org/olat/modules/grading/_spring/gradingContext.xml"/>
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties
index 5112ae519df12ed9964d1fc6315a11e8a5f7aadc..5e1006f2f98e81e5fbb529b002c5f3ab9ab42817 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties
@@ -11,7 +11,7 @@ adobeconnect.module.enabled.for.groups=Groups
 adobeconnect.module.provider=Provider
 adobeconnect.title=Adobe Connect
 check=Check the connection
-confirm.delete.meeting=Do you reallay want to delete the meeting "{0}"?
+confirm.delete.meeting=Do you really want to delete the meeting "{0}"?
 confirm.delete.meeting.title=Delete meeting "{0}"
 connection.failed=Login failed.
 connection.successful=Login successful\!
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f417ea429abdb57c822c52e83c1142ae22f331f
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
@@ -0,0 +1,78 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton;
+
+import java.util.List;
+
+import org.olat.core.id.Identity;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface BigBlueButtonManager {
+	
+	/**
+	 * Create and persist a meeting in OpenOlat. The method will generate
+	 * an unique meeting identifier and passwords for attendees and moderators.
+	 * 
+	 * @param name The name of the meeting
+	 * @param entry The repository entry (optional but this or group)
+	 * @param subIdent The sub-identifier (optional)
+	 * @param businessGroup The business group (optional but this or entry)
+	 * @return A meeting with some default values
+	 */
+	public BigBlueButtonMeeting createAndPersistMeeting(String name, RepositoryEntry entry, String subIdent, BusinessGroup businessGroup);
+
+	public BigBlueButtonMeeting getMeeting(BigBlueButtonMeeting meeting);
+	
+	public BigBlueButtonMeeting updateMeeting(BigBlueButtonMeeting meeting);
+	
+	public boolean deleteMeeting(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors);
+	
+	public BigBlueButtonMeetingTemplate createAndPersistTemplate(String name);
+
+	public List<BigBlueButtonMeetingTemplate> getTemplates();
+	
+	public BigBlueButtonMeetingTemplate updateTemplate(BigBlueButtonMeetingTemplate template);
+	
+	public void deleteTemplate(BigBlueButtonMeetingTemplate template);
+	
+	public boolean isTemplateInUse(BigBlueButtonMeetingTemplate template);
+	
+	public List<BigBlueButtonMeeting> getMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup);
+	
+
+	public List<BigBlueButtonMeeting> getAllMeetings();
+	
+	public String join(BigBlueButtonMeeting meeting, Identity identity, boolean moderator, boolean guest, BigBlueButtonErrors errors);
+	
+	public boolean isMeetingRunning(BigBlueButtonMeeting meeting);
+	
+	public boolean checkConnection(String url, String sharedSecret, BigBlueButtonErrors errors);
+	
+	
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5cf5a36af378b54dfe370062cd73749bac82332
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java
@@ -0,0 +1,90 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton;
+
+import java.util.Date;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+import org.olat.group.BusinessGroup;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface BigBlueButtonMeeting extends ModifiedInfo, CreateInfo {
+	
+	public Long getKey();
+	
+	public String getMeetingId();
+	
+	public String getAttendeePassword();
+
+	public String getModeratorPassword();
+	
+	public String getName();
+	
+	public void setName(String name);
+	
+	public String getDescription();
+	
+	public void setDescription(String description);
+	
+	public String getWelcome();
+	
+	public void setWelcome(String welcome);
+	
+	public boolean isPermanent();
+	
+	public void setPermanent(boolean permanent);
+	
+	public Date getStartDate();
+	
+	public void setStartDate(Date start);
+	
+	public long getLeadTime();
+	
+	public void setLeadTime(long leadTime);
+
+	public Date getStartWithLeadTime();
+	
+	public Date getEndDate();
+	
+	public void setEndDate(Date end);
+
+	public long getFollowupTime();
+	
+	public void setFollowupTime(long followupTime);
+
+	public Date getEndWithFollowupTime();
+	
+	public BigBlueButtonMeetingTemplate getTemplate();
+
+	public void setTemplate(BigBlueButtonMeetingTemplate template);
+	
+	public BusinessGroup getBusinessGroup();
+
+	public RepositoryEntry getEntry();
+
+	public String getSubIdent();
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1b3edff0973a37a7cae478c342c2c7b9fde0f07
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java
@@ -0,0 +1,100 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 19 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface BigBlueButtonMeetingTemplate extends ModifiedInfo, CreateInfo {
+	
+	public Long getKey();
+	
+	public boolean isSystem();
+	
+	public String getExternalId();
+	
+	public String getName();
+
+	public void setName(String name);
+
+	public String getDescription();
+
+	public void setDescription(String description);
+	
+	public Integer getMaxParticipants();
+
+	public void setMaxParticipants(Integer maxParticipants);
+	
+	public Boolean getMuteOnStart();
+
+	public void setMuteOnStart(Boolean muteOnStart);
+
+	public Boolean getAutoStartRecording();
+
+	public void setAutoStartRecording(Boolean autoStartRecording);
+
+	public Boolean getAllowStartStopRecording();
+
+	public void setAllowStartStopRecording(Boolean allowStartStopRecording);
+
+	public Boolean getWebcamsOnlyForModerator();
+
+	public void setWebcamsOnlyForModerator(Boolean webcamsOnlyForModerator);
+
+	public Boolean getAllowModsToUnmuteUsers();
+
+	public void setAllowModsToUnmuteUsers(Boolean allowModsToUnmuteUsers);
+	
+	public Boolean getLockSettingsDisableCam();
+
+	public void setLockSettingsDisableCam(Boolean lockSettingsDisableCam);
+
+	public Boolean getLockSettingsDisableMic();
+
+	public void setLockSettingsDisableMic(Boolean lockSettingsDisableMic);
+
+	public Boolean getLockSettingsDisablePrivateChat();
+
+	public void setLockSettingsDisablePrivateChat(Boolean lockSettingsDisablePrivateChat);
+
+	public Boolean getLockSettingsDisablePublicChat();
+
+	public void setLockSettingsDisablePublicChat(Boolean lockSettingsDisablePublicChat);
+
+	public Boolean getLockSettingsDisableNote();
+
+	public void setLockSettingsDisableNote(Boolean lockSettingsDisableNote);
+
+	public Boolean getLockSettingsLockedLayout();
+
+	public void setLockSettingsLockedLayout(Boolean lockSettingsLockedLayout);
+	
+	public GuestPolicyEnum getGuestPolicyEnum();
+
+	public void setGuestPolicyEnum(GuestPolicyEnum guestPolicy);
+
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonModule.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..157865ab7725b6193e0c89ce543cc8bb91ac2ab2
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonModule.java
@@ -0,0 +1,270 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton;
+
+import java.net.URI;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.olat.core.configuration.AbstractSpringModule;
+import org.olat.core.configuration.ConfigOnOff;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonModule extends AbstractSpringModule implements ConfigOnOff {
+	
+	private static final String PROP_ENABLED = "vc.bigbluebutton.enabled";
+	private static final String PROP_GROUP_ENABLED = "vc.bigbluebutton.groups";
+	private static final String PROP_COURSE_ENABLED = "vc.bigbluebutton.courses";
+	private static final String PROP_CLEAN_MEETINGS = "vc.bigbluebutton.cleanupMeetings";
+	private static final String PROP_DAYS_TO_KEEP = "vc.bigbluebutton.daysToKeep";
+	private static final String PROP_SECRET = "vc.bigbluebutton.secret";
+	private static final String PROP_SHARED_SECRET = "vc.bigbluebutton.shared.secret";
+	private static final String PROP_PROTOCOL = "vc.bigbluebutton.protocol";
+	private static final String PROP_PORT = "vc.bigbluebutton.port";
+	private static final String PROP_BASEURL = "vc.bigbluebutton.baseurl";
+	private static final String PROP_CONTEXTPATH = "vc.bigbluebutton.contextpath";
+	
+	@Value("${vc.bigbluebutton.enabled}")
+	private boolean enabled;
+	
+	@Value("${vc.bigbluebutton.protocol:https}")
+	private String protocol;
+	@Value("${vc.bigbluebutton.port:443}")
+	private int port;
+	@Value("${vc.bigbluebutton.baseurl}")
+	private String baseUrl;
+	@Value("${vc.bigbluebutton.context:/api/xml}")
+	private String contextPath;
+	
+	@Value("${vc.bigbluebutton.groups:true}")
+	private String groupsEnabled;
+	@Value("${vc.bigbluebutton.courses:true}")
+	private String coursesEnabled;	
+	@Value("${vc.bigbluebutton.cleanupMeetings:false}")
+	private String cleanupMeetings;
+	@Value("${vc.bigbluebutton.daysToKeep:}")
+	private String daysToKeep;
+	@Value("${vc.bigbluebutton.secret}")
+	private String secret;
+	@Value("${vc.bigbluebutton.shared.secret}")
+	private String sharedSecret;
+	
+	
+	@Autowired
+	public BigBlueButtonModule(CoordinatorManager coordinatorManager) {
+		super(coordinatorManager);
+	}
+	
+	@Override
+	public void init() {
+		String enabledObj = getStringPropertyValue(PROP_ENABLED, true);
+		if(StringHelper.containsNonWhitespace(enabledObj)) {
+			enabled = "true".equals(enabledObj);
+		}
+		
+		protocol = getStringPropertyValue(PROP_PROTOCOL, protocol);
+		String portObj = getStringPropertyValue(PROP_PORT, true);
+		if(StringHelper.containsNonWhitespace(portObj)) {
+			port = Integer.parseInt(portObj);
+		}
+		baseUrl = getStringPropertyValue(PROP_BASEURL, baseUrl);
+		contextPath = getStringPropertyValue(PROP_CONTEXTPATH, contextPath);
+		cleanupMeetings = getStringPropertyValue(PROP_CLEAN_MEETINGS, cleanupMeetings);
+		daysToKeep = getStringPropertyValue(PROP_DAYS_TO_KEEP, daysToKeep);
+		secret = getStringPropertyValue(PROP_SECRET, secret);
+		sharedSecret = getStringPropertyValue(PROP_SHARED_SECRET, sharedSecret);
+	}
+	
+	@Override
+	protected void initFromChangedProperties() {
+		init();
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return enabled;
+	}
+	
+	public void setEnabled(boolean enabled) {
+		this.enabled = enabled;
+		setBooleanProperty(PROP_ENABLED, enabled, true);
+	}
+	
+	public boolean isGroupsEnabled() {
+		return "true".equals(groupsEnabled);
+	}
+
+	public void setGroupsEnabled(boolean enabled) {
+		groupsEnabled = enabled ? "true" : "false";
+		setStringProperty(PROP_GROUP_ENABLED, groupsEnabled, true);
+	}
+
+	public boolean isCoursesEnabled() {
+		return "true".equals(coursesEnabled);
+	}
+
+	public void setCoursesEnabled(boolean enabled) {
+		coursesEnabled = enabled ? "true" : "false";
+		setStringProperty(PROP_COURSE_ENABLED, coursesEnabled, true);
+	}
+	
+	public URI getBigBlueButtonURI() {
+		if(StringHelper.containsNonWhitespace(baseUrl)) {
+			UriBuilder builder = getBigBlueButtonUriBuilder();
+			return builder.build();
+		}
+		return null;
+	}
+	
+	public UriBuilder getBigBlueButtonUriBuilder() {
+		String acProtocol = getProtocol();
+		UriBuilder builder = UriBuilder.fromUri(acProtocol + "://" + getBaseUrl());
+		int acPort = getPort();
+		if(acPort > 0
+				&& !(acPort == 443 && "https".equals(acProtocol))
+				&& !(acPort == 80 && "http".equals(acProtocol))) {
+			builder = builder.port(getPort());
+		}
+		if(StringHelper.containsNonWhitespace(getContextPath())) {
+			builder = builder.path(getContextPath());
+		}
+		return builder;
+	}
+	
+	public UriBuilder getBigBlueButtonHostUriBuilder() {
+		String acProtocol = getProtocol();
+		UriBuilder builder = UriBuilder.fromUri(acProtocol + "://" + getBaseUrl());
+		int acPort = getPort();
+		if(acPort > 0
+				&& !(acPort == 443 && "https".equals(acProtocol))
+				&& !(acPort == 80 && "http".equals(acProtocol))) {
+			builder = builder.port(getPort());
+		}
+		return builder;
+	}
+	
+	public void setBigBlueButtonURI(URI uri) {
+		if(uri == null) {
+			setBaseUrl(null);
+			setContextPath(null);
+			setProtocol(null);
+		} else {
+			String host = uri.getHost();
+			setBaseUrl(host);
+			int omPort = uri.getPort();
+			setPort(omPort);
+			String path = uri.getPath();
+			if(StringHelper.containsNonWhitespace(path) && path.startsWith("/")) {
+				path = path.substring(1, path.length());
+			}
+			setContextPath(path);
+			String scheme = uri.getScheme();
+			setProtocol(scheme);
+		}
+	}
+	
+	public String getBaseUrl() {
+		return baseUrl;
+	}
+
+	public void setBaseUrl(String baseUrl) {
+		this.baseUrl = baseUrl;
+		setStringProperty(PROP_BASEURL, baseUrl, true);
+	}
+	
+	public String getContextPath() {
+		return contextPath;
+	}
+	
+	public void setContextPath(String contextPath) {
+		this.contextPath = contextPath;
+		setStringProperty(PROP_CONTEXTPATH, contextPath, true);
+	}
+	
+	public String getProtocol() {
+		return protocol;
+	}
+
+	public void setProtocol(String protocol) {
+		this.protocol = protocol;
+		setStringProperty(PROP_PROTOCOL, protocol, true);
+	}
+	
+	public int getPort() {
+		return port;
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+		setStringProperty(PROP_PORT, String.valueOf(port), true);
+	}
+	
+	public String getSecret() {
+		return secret;
+	}
+	
+	public void setSecret(String secret) {
+		this.secret = secret;
+		setStringProperty(PROP_SECRET, secret, true);
+	}
+
+	public String getSharedSecret() {
+		return sharedSecret;
+	}
+
+	public void setSharedSecret(String sharedSecret) {
+		this.sharedSecret = sharedSecret;
+		setStringProperty(PROP_SHARED_SECRET, sharedSecret, true);
+	}
+
+	public boolean isCleanupMeetings() {
+		return "true".equals(cleanupMeetings);
+	}
+
+	public void setCleanupMeetings(boolean enable) {
+		cleanupMeetings = enable ? "true" : "false";
+		setStringProperty(PROP_CLEAN_MEETINGS, cleanupMeetings, true);
+	}
+	
+	public long getDaysToKeep() {
+		if(StringHelper.isLong(daysToKeep)) {
+			return Long.parseLong(daysToKeep);
+		}
+		return -1l;
+	}
+	
+	public void setDaysToKeep(String days) {
+		this.daysToKeep = days;
+		setStringProperty(PROP_DAYS_TO_KEEP, days, true);
+		
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/GuestPolicyEnum.java b/src/main/java/org/olat/modules/bigbluebutton/GuestPolicyEnum.java
new file mode 100644
index 0000000000000000000000000000000000000000..2835dd89b6da96e4e802ee64260d0c96e86cd70d
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/GuestPolicyEnum.java
@@ -0,0 +1,34 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum GuestPolicyEnum {
+
+	ALWAYS_ACCEPT,
+	ALWAYS_DENY,
+	ASK_MODERATOR
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/_spring/bigBlueButtonContext.xml b/src/main/java/org/olat/modules/bigbluebutton/_spring/bigBlueButtonContext.xml
new file mode 100644
index 0000000000000000000000000000000000000000..78d18bf756a43990441def0557a58fd80d3b8ccf
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/_spring/bigBlueButtonContext.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:context="http://www.springframework.org/schema/context" 
+	xsi:schemaLocation="
+  http://www.springframework.org/schema/beans 
+  http://www.springframework.org/schema/beans/spring-beans.xsd 
+  http://www.springframework.org/schema/context 
+  http://www.springframework.org/schema/context/spring-context.xsd">
+  
+	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
+		<property name="order" value="7210" />
+		<property name="actionController">	
+			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
+				<property name="className" value="org.olat.modules.bigbluebutton.ui.BigBlueButtonAdminController"/>
+			</bean>
+		</property>
+		<property name="navigationKey" value="bigbluebutton" />
+		<property name="parentTreeNodeIdentifier" value="externalToolsParent" /> 
+		<property name="i18nActionKey" value="admin.menu.title"/>
+		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
+		<property name="translationPackage" value="org.olat.modules.bigbluebutton.ui"/>
+		<property name="extensionPoints">
+			<list>	
+				<value>org.olat.admin.SystemAdminMainController</value>		
+			</list>
+		</property>
+	</bean>
+	
+	<bean id="bigbluebuttonCleanupTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
+    	<property name="jobDetail" ref="bigbluebuttonConnectCleanupJob" />
+    	<property name="cronExpression" value="0 47 2 * * ?"/>
+    	<property name="startDelay" value="45000" />
+	</bean>
+	
+	<bean id="bigbluebuttonConnectCleanupJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean" lazy-init="true">
+		<property name="jobClass" value="org.olat.modules.bigbluebutton.manager.BigBlueButtonCleanupJob" />
+	</bean>
+	
+</beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonCleanupJob.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonCleanupJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..745f684e436b932e5239abfb24521c2ae7a77717
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonCleanupJob.java
@@ -0,0 +1,44 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import org.apache.logging.log4j.Logger;
+import org.olat.core.commons.services.scheduler.JobWithDB;
+import org.olat.core.logging.Tracing;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@DisallowConcurrentExecution
+public class BigBlueButtonCleanupJob extends JobWithDB {
+
+	private static final Logger log = Tracing.createLoggerFor(BigBlueButtonCleanupJob.class);
+	
+	@Override
+	public void executeWithDB(JobExecutionContext arg0) throws JobExecutionException {
+		log.info("", "Clean Big Blue Button meetings");
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d6a29d7bb0ede5676a18f2c06652ed5f8ffcbcf
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
@@ -0,0 +1,409 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.logging.log4j.Logger;
+import org.olat.commons.calendar.CalendarManagedFlag;
+import org.olat.commons.calendar.CalendarManager;
+import org.olat.commons.calendar.model.Kalendar;
+import org.olat.commons.calendar.model.KalendarEvent;
+import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
+import org.olat.core.id.Identity;
+import org.olat.core.id.User;
+import org.olat.core.id.context.BusinessControlFactory;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.CodeHelper;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.WebappHelper;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.modules.bigbluebutton.BigBlueButtonModule;
+import org.olat.modules.bigbluebutton.GuestPolicyEnum;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonError;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrorCodes;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.manager.RepositoryEntryDAO;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.w3c.dom.Document;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonManagerImpl implements BigBlueButtonManager, InitializingBean {
+	
+	private static final Logger log = Tracing.createLoggerFor(BigBlueButtonManagerImpl.class);
+
+	@Autowired
+	private CalendarManager calendarManager;
+	@Autowired
+	private RepositoryEntryDAO repositoryEntryDao;
+	@Autowired
+	private BigBlueButtonModule bigBlueButtonModule;
+	@Autowired
+	private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao;
+	@Autowired
+	private BigBlueButtonMeetingTemplateDAO bigBlueButtonMeetingTemplateDao;
+	
+	@Override
+	public void afterPropertiesSet() throws Exception {
+		List<BigBlueButtonMeetingTemplate> templates = bigBlueButtonMeetingTemplateDao.getTemplates();
+		
+		// Web conferen
+		defaultTemplate("web-conference", "Web conference", 100,
+				Boolean.FALSE, Boolean.FALSE, Boolean.TRUE, // recording
+				Boolean.TRUE, Boolean.TRUE, // webcams, unmute
+				Boolean.TRUE, Boolean.TRUE, // cam, mic
+				Boolean.FALSE, Boolean.TRUE, // chat
+				Boolean.FALSE, Boolean.FALSE, // node, layout
+				GuestPolicyEnum.ALWAYS_ACCEPT, templates);
+		
+		defaultTemplate("web-classe", "Classes / Klasse", 25,
+				Boolean.FALSE, Boolean.FALSE, Boolean.TRUE, // recording
+				Boolean.FALSE, Boolean.TRUE, // webcams, unmute
+				Boolean.FALSE, Boolean.FALSE, // cam, mic
+				Boolean.FALSE, Boolean.FALSE, // chat
+				Boolean.FALSE, Boolean.FALSE, // node, layout
+				GuestPolicyEnum.ALWAYS_DENY, templates);
+		
+		defaultTemplate("web-one-to-one", "One to one", 2,
+				Boolean.FALSE, Boolean.FALSE, Boolean.TRUE, // recording
+				Boolean.FALSE, Boolean.TRUE, // webcams, unmute
+				Boolean.FALSE, Boolean.FALSE, // cam, mic
+				Boolean.TRUE, Boolean.FALSE, // chat
+				Boolean.FALSE, Boolean.FALSE, // node, layout
+				GuestPolicyEnum.ALWAYS_DENY, templates);
+	}
+	
+	private void defaultTemplate(String externalId, String name, Integer maxParticipants,
+			Boolean muteOnStart, Boolean autoStartRecording, Boolean allowStartStopRecording,
+			Boolean webcamsOnlyForModerator, Boolean allowModsToUnmuteUsers,
+			Boolean lockSettingsDisableCam, Boolean lockSettingsDisableMic,
+			Boolean lockSettingsDisablePrivateChat, Boolean lockSettingsDisablePublicChat,
+			Boolean lockSettingsDisableNote, Boolean lockSettingsLockedLayout,
+			GuestPolicyEnum guestPolicy, List<BigBlueButtonMeetingTemplate> templates) {
+		
+		BigBlueButtonMeetingTemplate template = templates.stream()
+				.filter(tpl -> externalId.equals(tpl.getExternalId()))
+				.findFirst().orElse(null);
+		if(template == null) {
+			template = bigBlueButtonMeetingTemplateDao.createTemplate(name, externalId, true);
+		}
+		template.setMaxParticipants(maxParticipants);
+		template.setMuteOnStart(muteOnStart);
+		template.setAutoStartRecording(autoStartRecording);
+		template.setAllowStartStopRecording(allowStartStopRecording);
+		template.setWebcamsOnlyForModerator(webcamsOnlyForModerator);
+		template.setAllowModsToUnmuteUsers(allowModsToUnmuteUsers);
+		template.setLockSettingsDisableCam(lockSettingsDisableCam);
+		template.setLockSettingsDisableMic(lockSettingsDisableMic);
+		template.setLockSettingsDisablePrivateChat(lockSettingsDisablePrivateChat);
+		template.setLockSettingsDisablePublicChat(lockSettingsDisablePublicChat);
+		template.setLockSettingsDisableNote(lockSettingsDisableNote);
+		template.setLockSettingsLockedLayout(lockSettingsLockedLayout);
+		template.setGuestPolicyEnum(guestPolicy);
+		bigBlueButtonMeetingTemplateDao.updateTemplate(template);
+	}
+
+	@Override
+	public BigBlueButtonMeeting createAndPersistMeeting(String name, RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
+		return bigBlueButtonMeetingDao.createAndPersistMeeting(name, entry, subIdent, businessGroup);
+	}
+	
+	@Override
+	public BigBlueButtonMeeting getMeeting(BigBlueButtonMeeting meeting) {
+		return bigBlueButtonMeetingDao.loadByKey(meeting.getKey());
+	}
+
+	@Override
+	public BigBlueButtonMeeting updateMeeting(BigBlueButtonMeeting meeting) {
+		updateCalendarEvent(meeting);
+		return bigBlueButtonMeetingDao.updateMeeting(meeting);
+	}	
+
+	@Override
+	public BigBlueButtonMeetingTemplate createAndPersistTemplate(String name) {
+		return bigBlueButtonMeetingTemplateDao.createTemplate(name, null, false);
+	}
+	
+	@Override
+	public BigBlueButtonMeetingTemplate updateTemplate(BigBlueButtonMeetingTemplate template) {
+		return bigBlueButtonMeetingTemplateDao.updateTemplate(template);
+	}
+
+	@Override
+	public void deleteTemplate(BigBlueButtonMeetingTemplate template) {
+		bigBlueButtonMeetingTemplateDao.deleteTemplate(template);
+	}
+
+	@Override
+	public boolean isTemplateInUse(BigBlueButtonMeetingTemplate template) {
+		return bigBlueButtonMeetingTemplateDao.isTemplateInUse(template);
+	}
+
+	@Override
+	public List<BigBlueButtonMeetingTemplate> getTemplates() {
+		return bigBlueButtonMeetingTemplateDao.getTemplates();
+	}
+
+	@Override
+	public List<BigBlueButtonMeeting> getAllMeetings() {
+		return bigBlueButtonMeetingDao.getAllMeetings();
+	}
+
+	@Override
+	public List<BigBlueButtonMeeting> getMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
+		return bigBlueButtonMeetingDao.getMeetings(entry, subIdent, businessGroup);
+	}
+	
+	@Override
+	public boolean deleteMeeting(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) {
+		BigBlueButtonMeeting reloadedMeeting = bigBlueButtonMeetingDao.loadByKey(meeting.getKey());
+		removeCalendarEvent(reloadedMeeting);
+		bigBlueButtonMeetingDao.deleteMeeting(reloadedMeeting);
+		return false;
+	}
+	
+	private void removeCalendarEvent(BigBlueButtonMeeting meeting) {
+		Kalendar calendar = getCalendar(meeting);
+		if(calendar == null) return;
+		
+		String externalId = generateEventExternalId(meeting);
+		List<KalendarEvent> events = calendar.getEvents();
+		for(KalendarEvent event:events) {
+			if(meeting.getMeetingId().equals(externalId)) {
+				calendarManager.removeEventFrom(calendar, event);
+			}
+		}
+	}
+	
+	private void updateCalendarEvent(BigBlueButtonMeeting meeting) {
+		Kalendar calendar = getCalendar(meeting);
+		if(calendar == null) return;
+		
+		String externalId = generateEventExternalId(meeting);
+		List<KalendarEvent> events = calendar.getEvents();
+		for(KalendarEvent event:events) {
+			if(event.getExternalId().equals(externalId)) {
+				if(meeting.isPermanent()) {
+					calendarManager.removeEventFrom(calendar, event);
+				} else {
+					event.setBegin(meeting.getStartDate());
+					event.setEnd(meeting.getEndDate());
+					calendarManager.updateEventFrom(calendar, event);
+				}
+				return;
+			}
+		}
+		
+		if(!meeting.isPermanent()) {
+			String eventId = CodeHelper.getGlobalForeverUniqueID();
+			KalendarEvent newEvent = new KalendarEvent(eventId, null, meeting.getName(), meeting.getStartDate(), meeting.getEndDate());
+			newEvent.setDescription(meeting.getDescription());
+			CalendarManagedFlag[] managedFlags = {
+					CalendarManagedFlag.all
+			};
+			newEvent.setManagedFlags(managedFlags);
+			newEvent.setExternalId(externalId);
+			calendarManager.addEventTo(calendar, newEvent);
+		}
+	}
+	
+	private String generateEventExternalId(BigBlueButtonMeeting meeting) {
+		return "bigbluebutton-".concat(meeting.getMeetingId());
+	}
+	
+	private Kalendar getCalendar(BigBlueButtonMeeting meeting) {
+		KalendarRenderWrapper wrapper = null;
+		if(meeting.getBusinessGroup() != null) {
+			wrapper = calendarManager.getGroupCalendar(meeting.getBusinessGroup());
+		} else if(meeting.getEntry() != null) {
+			RepositoryEntry entry = repositoryEntryDao.loadByKey(meeting.getEntry().getKey());
+			ICourse course = CourseFactory.loadCourse(entry);
+			wrapper = calendarManager.getCourseCalendar(course);
+		}
+		return wrapper == null ? null: wrapper.getKalendar();
+	}
+
+	@Override
+	public boolean isMeetingRunning(BigBlueButtonMeeting meeting) {
+		BigBlueButtonUriBuilder uriBuilder = getUriBuilder();
+		uriBuilder
+			.operation("isMeetingRunning")
+			.parameter("meetingID", meeting.getMeetingId());
+		
+		BigBlueButtonErrors errors = new BigBlueButtonErrors();
+		Document doc = sendRequest(uriBuilder, errors);
+		if(doc == null || errors.hasErrors() || !BigBlueButtonUtils.checkSuccess(doc, errors)) {
+			return false;
+		}
+		
+		String running = BigBlueButtonUtils.getFirstElementValue(doc.getDocumentElement(), "running");
+		return "true".equals(running);
+	}
+
+	@Override
+	public String join(BigBlueButtonMeeting meeting, Identity identity, boolean moderator, boolean guest, BigBlueButtonErrors errors) {
+		String joinUrl = null;
+		if(createBigBlueButtonMeeting(meeting, errors)) {
+			joinUrl = buildJoinUrl(meeting, identity, moderator, guest);
+		}
+		return joinUrl;
+	}
+	
+	private String buildJoinUrl(BigBlueButtonMeeting meeting, Identity identity, boolean moderator, boolean guest) {
+		String password = moderator ? meeting.getModeratorPassword() : meeting.getAttendeePassword();
+		
+		String userId = null;
+		if(!guest) {
+			userId = WebappHelper.getInstanceId() + "-" + identity.getKey();
+		}
+
+		BigBlueButtonUriBuilder uriBuilder = getUriBuilder();
+		return uriBuilder
+			.operation("join")
+			.parameter("meetingID", meeting.getMeetingId())
+			.parameter("fullName", getFullName(identity))
+			.parameter("password", password)
+			.optionalParameter("userID", userId)
+			.build()
+			.toString();
+	}
+	
+	private String getFullName(Identity identity) {
+		StringBuilder sb = new StringBuilder(32);
+		User user = identity.getUser();
+		if(StringHelper.containsNonWhitespace(user.getFirstName())) {
+			sb.append(user.getFirstName());
+		}
+		if(StringHelper.containsNonWhitespace(user.getLastName())) {
+			if(sb.length() > 0) sb.append(" ");
+			sb.append(user.getLastName());
+		}
+		return sb.length() == 0 ? "John Smith" : sb.toString();
+	}
+	
+	private String getBusinessPath(BigBlueButtonMeeting meeting) {
+		String businessPath;
+		if(meeting.getEntry() != null) {
+			businessPath = "[RepositoryEntry:" + meeting.getEntry().getKey() + "]";
+			if(StringHelper.containsNonWhitespace(meeting.getSubIdent())) {
+				businessPath = "[CourseNode:" + meeting.getSubIdent() + "]";
+			}
+		} else if(meeting.getBusinessGroup() != null) {
+
+			businessPath = "[BusinessGroup:" + meeting.getBusinessGroup().getKey() + "]";
+		} else {
+			businessPath = "[RepositoryEntry:0]";
+		}
+		return BusinessControlFactory.getInstance().getURLFromBusinessPathString(businessPath);
+	}
+
+	private boolean createBigBlueButtonMeeting(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) {
+		BigBlueButtonMeetingTemplate template = meeting.getTemplate();
+		
+		BigBlueButtonUriBuilder uriBuilder = getUriBuilder();
+		uriBuilder
+			.operation("create")
+			.optionalParameter("name", meeting.getName())
+			.parameter("meetingID", meeting.getMeetingId())
+			.optionalParameter("welcome", meeting.getWelcome())
+			.optionalParameter("attendeePW", meeting.getAttendeePassword())
+			.optionalParameter("moderatorPW", meeting.getModeratorPassword())
+			.optionalParameter("logoutURL", getBusinessPath(meeting));
+		if(meeting.getStartWithLeadTime() != null && meeting.getEndWithFollowupTime() != null) {
+			long start = meeting.getStartWithLeadTime().getTime();
+			long end = meeting.getEndWithFollowupTime().getTime();
+			long duration = (end - start) / (60l * 1000l);
+			uriBuilder.optionalParameter("duration", Long.toString(duration + 1));// + 1 for rounding error
+		}
+
+		if(template != null) {
+			uriBuilder
+				.optionalParameter("maxParticipants", template.getMaxParticipants())
+				.optionalParameter("record", (String)null)
+				// video options
+				.optionalParameter("muteOnStart", template.getMuteOnStart())
+				.optionalParameter("autoStartRecording", template.getAutoStartRecording())
+				.optionalParameter("allowStartStopRecording", template.getAllowStartStopRecording())
+				.optionalParameter("webcamsOnlyForModerator", template.getWebcamsOnlyForModerator())
+				.optionalParameter("allowModsToUnmuteUsers", template.getAllowModsToUnmuteUsers())
+				// lock settings
+				.optionalParameter("lockSettingsDisableCam", template.getLockSettingsDisableCam())
+				.optionalParameter("lockSettingsDisableMic", template.getLockSettingsDisableMic())
+				.optionalParameter("lockSettingsDisablePrivateChat", template.getLockSettingsDisablePrivateChat())
+				.optionalParameter("lockSettingsDisablePublicChat", template.getLockSettingsDisablePublicChat())
+				.optionalParameter("lockSettingsDisableNote", template.getLockSettingsDisableNote())
+				.optionalParameter("lockSettingsLockedLayout", template.getLockSettingsLockedLayout())
+				// guest policy
+				.optionalParameter("guestPolicy", template.getGuestPolicyEnum().name());
+		}
+		
+		Document doc = sendRequest(uriBuilder, errors);
+		return BigBlueButtonUtils.checkSuccess(doc, errors);
+	}
+	
+	@Override
+	public boolean checkConnection(String url, String sharedSecret, BigBlueButtonErrors errors) {
+		BigBlueButtonUriBuilder uriBuilder = BigBlueButtonUriBuilder.fromUri(URI.create(url), sharedSecret);
+		uriBuilder.operation("getMeetings");
+		Document doc = sendRequest(uriBuilder, errors);
+		if(doc != null) {
+			return BigBlueButtonUtils.checkSuccess(doc, errors);
+		}
+		return false;
+	}
+	
+	private BigBlueButtonUriBuilder getUriBuilder() {
+		return BigBlueButtonUriBuilder.fromUri(bigBlueButtonModule.getBigBlueButtonURI(), bigBlueButtonModule.getSharedSecret());	
+	}
+	
+	protected Document sendRequest(BigBlueButtonUriBuilder builder, BigBlueButtonErrors errors) {
+		URI uri = builder.build();
+		HttpGet get = new HttpGet(uri);
+		try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+				CloseableHttpResponse response = httpClient.execute(get)) {
+			int statusCode = response.getStatusLine().getStatusCode();
+			log.debug("Status code of: {} {}", uri, statusCode);
+			return BigBlueButtonUtils.getDocumentFromEntity(response.getEntity());
+		} catch(Exception e) {
+			errors.append(new BigBlueButtonError(BigBlueButtonErrorCodes.unkown));
+			log.error("", e);
+			return null;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..94540374f495cc98142fe795d4fc650d0abaa3c0
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
@@ -0,0 +1,182 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import javax.persistence.TypedQuery;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.persistence.QueryBuilder;
+import org.olat.core.util.StringHelper;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonMeetingDAO {
+
+	@Autowired
+	private DB dbInstance;
+	
+	public BigBlueButtonMeeting createAndPersistMeeting(String name,
+			RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
+		BigBlueButtonMeetingImpl meeting = new BigBlueButtonMeetingImpl();
+		meeting.setCreationDate(new Date());
+		meeting.setLastModified(meeting.getCreationDate());
+		meeting.setName(name);
+		meeting.setMeetingId(UUID.randomUUID().toString());
+		meeting.setAttendeePassword(UUID.randomUUID().toString());
+		meeting.setModeratorPassword(UUID.randomUUID().toString());
+		
+		meeting.setEntry(entry);
+		meeting.setSubIdent(subIdent);
+		meeting.setBusinessGroup(businessGroup);
+		
+		dbInstance.getCurrentEntityManager().persist(meeting);
+		return meeting;
+	}
+	
+	public BigBlueButtonMeeting loadByKey(Long key) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select meeting from bigbluebuttonmeeting as meeting")
+		  .append(" left join fetch meeting.entry as entry")
+		  .append(" left join fetch meeting.businessGroup as businessGroup")
+		  .append(" left join fetch meeting.template as template")
+		  .append(" where meeting.key=:meetingKey");
+		
+		List<BigBlueButtonMeeting> meetings = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), BigBlueButtonMeeting.class)
+				.setParameter("meetingKey", key)
+				.getResultList();
+		return meetings == null || meetings.isEmpty() ? null : meetings.get(0);
+	}
+	
+	public BigBlueButtonMeeting updateMeeting(BigBlueButtonMeeting meeting) {
+		meeting.setLastModified(new Date());
+		updateDates((BigBlueButtonMeetingImpl)meeting,
+				meeting.getStartDate(), meeting.getLeadTime(), meeting.getEndDate(), meeting.getFollowupTime());
+		return dbInstance.getCurrentEntityManager().merge(meeting);
+	}
+	
+	public void deleteMeeting(BigBlueButtonMeeting meeting) {
+		dbInstance.getCurrentEntityManager().remove(meeting);
+	}
+	
+	private void updateDates(BigBlueButtonMeetingImpl meet, Date start, long leadTime, Date end, long followupTime) {
+		Calendar cal = Calendar.getInstance();
+		if(start == null) {
+			meet.setStartDate(null);
+			meet.setLeadTime(0);
+			meet.setStartWithLeadTime(null);
+		} else {
+			start = cleanDate(start);
+			if(leadTime > 0) {
+				cal.add(Calendar.MINUTE, -(int)leadTime);
+			}
+			meet.setStartDate(start);
+			meet.setLeadTime(leadTime);
+			meet.setStartWithLeadTime(cal.getTime());
+		}
+		
+		if(end == null) {
+			meet.setEndDate(null);
+			meet.setFollowupTime(0);
+			meet.setEndWithFollowupTime(null);
+		} else {
+			end = cleanDate(end);
+			cal.setTime(end);
+			if(followupTime > 0) {
+				cal.add(Calendar.MINUTE, (int)followupTime);
+			}
+			meet.setEndDate(end);
+			meet.setFollowupTime(followupTime);
+			meet.setEndWithFollowupTime(cal.getTime());
+		}
+	}
+	
+	/**
+	 * Remove seconds and milliseconds.
+	 * 
+	 * @return A date without seconds and milliseconds
+	 */
+	private Date cleanDate(Date date) {
+		if(date == null) return null;
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		cal.set(Calendar.SECOND, 0);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal.getTime();
+	}
+	
+	public List<BigBlueButtonMeeting> getAllMeetings() {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select meeting from bigbluebuttonmeeting as meeting")
+		  .append(" left join fetch meeting.entry as entry")
+		  .append(" left join fetch meeting.businessGroup as businessGroup")
+		  .append(" left join fetch meeting.template as template");
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), BigBlueButtonMeeting.class)
+				.getResultList();
+	}
+	
+	public List<BigBlueButtonMeeting> getMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select meeting from bigbluebuttonmeeting as meeting")
+		  .append(" left join fetch meeting.template as template");
+		if(entry != null) {
+			sb.and().append("meeting.entry.key=:entryKey");
+			if(StringHelper.containsNonWhitespace(subIdent)) {
+				sb.and().append("meeting.subIdent=:subIdent");
+			}
+		}
+		if(businessGroup != null) {
+			sb.and().append("meeting.businessGroup.key=:businessGroupKey");
+		}
+		
+		TypedQuery<BigBlueButtonMeeting> query = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), BigBlueButtonMeeting.class);
+		
+		if(entry != null) {
+			query.setParameter("entryKey", entry.getKey());
+			sb.and().append("meeting.entry.key=:entryKey");
+			if(StringHelper.containsNonWhitespace(subIdent)) {
+				query.setParameter("subIdent", subIdent);
+			}
+		}
+		if(businessGroup != null) {
+			query.setParameter("businessGroupKey", businessGroup.getKey());
+		}
+		return query.getResultList();
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingTemplateDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingTemplateDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..065eae80974025f83db0b619577b7afe5058dae9
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingTemplateDAO.java
@@ -0,0 +1,102 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.persistence.QueryBuilder;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingTemplateImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 19 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonMeetingTemplateDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public BigBlueButtonMeetingTemplate createTemplate(String name, String externalId, boolean system) {
+		BigBlueButtonMeetingTemplateImpl template = new BigBlueButtonMeetingTemplateImpl();
+		template.setCreationDate(new Date());
+		template.setLastModified(template.getCreationDate());
+		template.setName(name);
+		template.setSystem(system);
+		template.setExternalId(externalId);
+		dbInstance.getCurrentEntityManager().persist(template);
+		return template;
+	}
+	
+	public BigBlueButtonMeetingTemplate updateTemplate(BigBlueButtonMeetingTemplate template) {
+		((BigBlueButtonMeetingTemplateImpl)template).setLastModified(new Date());
+		return dbInstance.getCurrentEntityManager().merge(template);
+	}
+	
+	public void deleteTemplate(BigBlueButtonMeetingTemplate template) {
+		BigBlueButtonMeetingTemplate templateToDelete = dbInstance.getCurrentEntityManager()
+			.getReference(BigBlueButtonMeetingTemplateImpl.class, template.getKey());
+		dbInstance.getCurrentEntityManager().remove(templateToDelete);
+	}
+	
+	public BigBlueButtonMeetingTemplate getTemplate(BigBlueButtonMeetingTemplate template) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select template from bigbluebuttontemplate as template")
+		  .append(" where template.key=:templateKey");
+
+		List<BigBlueButtonMeetingTemplate> templates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), BigBlueButtonMeetingTemplate.class)
+				.setParameter("templateKey", template.getKey())
+				.getResultList();
+		return templates == null || templates.isEmpty() ? null : templates.get(0);
+	}
+	
+	public boolean isTemplateInUse(BigBlueButtonMeetingTemplate template) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select meeting.key from bigbluebuttonmeeting as meeting")
+		  .append(" where meeting.template.key=:templateKey");
+
+		List<Long> templates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
+				.setParameter("templateKey", template.getKey())
+				.setFirstResult(0)
+				.setMaxResults(1)
+				.getResultList();
+		return templates != null && !templates.isEmpty()
+				&& templates.get(0) != null && templates.get(0).longValue() > 0;
+	}
+	
+	public List<BigBlueButtonMeetingTemplate> getTemplates() {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select template from bigbluebuttontemplate as template");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), BigBlueButtonMeetingTemplate.class)
+				.getResultList();
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUriBuilder.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUriBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..7779d2c80564cd2286dbd18074a8896a905e7400
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUriBuilder.java
@@ -0,0 +1,188 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonUriBuilder {
+
+	private static final Logger log = LogManager.getLogger(BigBlueButtonUriBuilder.class);
+	
+	private String host;
+	private String scheme;
+	private int port;
+	private String startPath;
+
+	private final String sharedSecret;
+	
+	private String operation;
+	private final List<Parameter> parameters = new ArrayList<>();
+
+    private BigBlueButtonUriBuilder(URI uri, String sharedSecret) {
+        this.sharedSecret = sharedSecret;
+    	if(uri.getScheme() != null) {
+    		scheme = uri.getScheme();
+    	} else {
+    		scheme = "https";
+    	}
+    	port = uri.getPort();
+    	host = uri.getHost();
+    	startPath = uri.getPath();
+    }
+
+    public static BigBlueButtonUriBuilder fromUri(URI uri, String sharedSecret) {
+    	return new BigBlueButtonUriBuilder(uri, sharedSecret);
+    }
+    
+    public BigBlueButtonUriBuilder operation(String operation) {
+    	this.operation = operation;
+    	return this;
+    }
+
+    public BigBlueButtonUriBuilder parameter(String parameter, String value) {
+    	parameters.add(new Parameter(parameter, value));
+        return this;
+    }
+    
+    public BigBlueButtonUriBuilder optionalParameter(String parameter, String value) {
+    	if(StringHelper.containsNonWhitespace(value)) {
+    		parameters.add(new Parameter(parameter, value));
+    	}
+        return this;
+    }
+    
+    public BigBlueButtonUriBuilder optionalParameter(String parameter, Integer value) {
+    	if(value != null) {
+    		parameters.add(new Parameter(parameter, value.toString()));
+    	}
+        return this;
+    }
+    
+    public BigBlueButtonUriBuilder optionalParameter(String parameter, Boolean value) {
+    	if(value != null) {
+    		parameters.add(new Parameter(parameter, value.toString()));
+    	}
+        return this;
+    }
+    
+    public URI build() {
+    	StringBuilder path = new StringBuilder();
+    	path.append(scheme).append("://")
+    	    .append(host);
+ 
+    	if(startPath != null) {
+    		appendPath(startPath, path);
+    	}
+    	appendPath("api", path);
+    	if(StringHelper.containsNonWhitespace(operation)) {
+    		appendPath(operation, path);
+    	}
+
+    	StringBuilder query = new StringBuilder();
+    	if(!parameters.isEmpty()) {
+    		for(Parameter param:parameters) {
+    			if(query.length() > 0) {
+    				query.append("&");
+    			}
+        		query
+        			.append(param.getParameter())
+        			.append("=")
+        			.append(urlEncode(param.getValue()));
+    		}	
+    	}
+    	
+    	String queryString = query.toString();
+    	queryString += "&checksum=" + checksum(operation, queryString, sharedSecret);
+    	path.append("?").append(queryString);
+    	
+    	try {
+    		return URI.create(path.toString());
+		} catch (Exception e) {
+			log.error("Cannot build URI: {} {} {}", scheme, host, port, e);
+			return null;
+		}
+    }
+    
+    public static String urlEncode(String s) {
+    	try {
+    		return URLEncoder.encode(s, "UTF-8");
+    	} catch (Exception e) {
+    		log.error("", e);
+    	}
+    	return "";
+    }
+    
+    private String checksum(String operation, String query, String salt) {
+    	String text = operation + query + salt;
+    	return checksum(text);
+    }
+    
+    private static String checksum(String s) {
+    	String checksum = "";
+    	try {
+    		checksum = org.apache.commons.codec.digest.DigestUtils.sha256Hex(s);
+    	} catch (Exception e) {
+    		log.error("Cannot calculate checksum", e);
+    	}
+    	return checksum;
+    }
+    
+    private static final StringBuilder appendPath(String path, StringBuilder sb) {
+    	if(path != null && path.length() > 0) {
+    		if((sb.length() == 0 || sb.charAt(sb.length() -1) != '/') && !path.startsWith("/")) {
+    			sb.append("/");
+    		}
+    		sb.append(path);
+    	}
+    	return sb;
+    }
+    
+    private static class Parameter {
+    	
+    	private final String parameter;
+    	private final String value;
+    	
+    	public Parameter(String parameter, String value) {
+    		this.parameter = parameter;
+    		this.value = value;
+    	}
+
+		public String getParameter() {
+			return parameter;
+		}
+
+		public String getValue() {
+			return value;
+		}
+    }
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6010d3a5c6f1b7cda9cc9dc190eadda11cf4301
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java
@@ -0,0 +1,135 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.http.HttpEntity;
+import org.apache.logging.log4j.Logger;
+import org.olat.core.logging.Tracing;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonError;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonUtils {
+	
+	private static final Logger log = Tracing.createLoggerFor(BigBlueButtonUtils.class);
+	
+	public static final String SUCCESS = "SUCCESS";
+	public static final String FAILED = "FAILED";
+	public static final String CHECKSUM_ERROR = "checksumError";
+	
+    protected static Document getDocumentFromEntity(String content) throws Exception {
+    	try(InputStream in=new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
+	        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+	        dbFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+	        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+	        return dBuilder.parse(in);
+    	} catch(Exception e) {
+    		throw e;
+    	}
+    }
+
+    protected static Document getDocumentFromEntity(HttpEntity entity) throws Exception {
+    	try(InputStream in=entity.getContent()) {
+	        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+	        dbFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+	        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+	        Document doc = dBuilder.parse(in);
+	        print(doc);
+	        return doc;
+    	} catch(Exception e) {
+    		throw e;
+    	}
+    }
+    
+    protected static boolean checkSuccess(Document document, BigBlueButtonErrors errors) {
+    	String returnCode = getFirstElementValue(document.getDocumentElement(), "returncode");
+    	print(document);
+    	if(SUCCESS.equals(returnCode)) {
+    		return true;
+    	} else if(FAILED.equals(returnCode)) {
+    		String messageKey = getFirstElementValue(document.getDocumentElement(), "messageKey");
+    		String message = getFirstElementValue(document.getDocumentElement(), "message");
+    		errors.append(new BigBlueButtonError(message, messageKey));
+    	}
+    	return false;
+    }
+    
+    protected static String getFirstElementValue(Element parent, String tagName) {
+        Element element = getFirstElement(parent, tagName);
+        return (element == null) ? "" : getCharacterDataFromElement(element);
+    }
+    
+    protected static Element getFirstElement(Element parent, String tagName) {
+        NodeList nodes = parent.getElementsByTagName(tagName);
+        return (nodes != null && nodes.getLength() > 0) ? (Element) (nodes.item(0)) : null;
+    }
+    
+    protected static String getCharacterDataFromElement(Element e) {
+    	StringBuilder sb = new StringBuilder();
+    	for(Node child = e.getFirstChild(); child != null; child = child.getNextSibling()) {
+    		if (child instanceof CharacterData) {
+            	CharacterData cd = (CharacterData)child;
+                sb.append(cd.getData());
+            }
+    	}
+        return sb.toString();
+    }
+    
+	protected static void print(Document document) {
+		if(log.isDebugEnabled()) {
+		    try(StringWriter writer = new StringWriter()) {
+		    	TransformerFactory factory = TransformerFactory.newInstance();
+				factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+				Transformer transformer = factory.newTransformer();
+				Source source = new DOMSource(document);
+				transformer.transform(source, new StreamResult(writer));
+				writer.flush();
+				log.info(writer.toString());
+			} catch (Exception e) {
+				log.error("", e);
+			}
+		}
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonError.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonError.java
new file mode 100644
index 0000000000000000000000000000000000000000..42ed9d8bc2c3e4116ad65f6e35eb6d330d07cce2
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonError.java
@@ -0,0 +1,71 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.model;
+
+import java.io.Serializable;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonError implements Serializable {
+
+	private static final long serialVersionUID = 6820811247631455090L;
+	private BigBlueButtonErrorCodes code;
+	private String[] arguments;
+	
+	private String messageKey;
+	private String message;
+	
+	public BigBlueButtonError(String message, String messageKey) {
+		this.message = message;
+		this.messageKey = messageKey;
+	}
+	
+	public BigBlueButtonError(BigBlueButtonErrorCodes code) {
+		this.code = code;
+	}
+	
+	public String getMessageKey() {
+		return messageKey;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public BigBlueButtonErrorCodes getCode() {
+		return code;
+	}
+	
+	public void setCode(BigBlueButtonErrorCodes code) {
+		this.code = code;
+	}
+
+	public String[] getArguments() {
+		return arguments;
+	}
+
+	public void setArguments(String[] arguments) {
+		this.arguments = arguments;
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java
new file mode 100644
index 0000000000000000000000000000000000000000..b72a057433a41be7591855d1230bc6446286ff5e
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java
@@ -0,0 +1,36 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.model;
+
+/**
+ * 
+ * Initial date: 18 avr. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum BigBlueButtonErrorCodes {
+	
+	sharedSecretDenied,
+	deletedObject,
+	serverNotAvailable,
+	unkown
+	;
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrors.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrors.java
new file mode 100644
index 0000000000000000000000000000000000000000..e74d15cec7ce2b4b3650866216a37b7ae3721bfc
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrors.java
@@ -0,0 +1,63 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonErrors implements Serializable {
+
+	private static final long serialVersionUID = -7927445328496097183L;
+	private final List<BigBlueButtonError> errors = new ArrayList<>(2);
+	
+	public List<BigBlueButtonError> getErrors() {
+		return errors;
+	}
+	
+	public void append(BigBlueButtonErrors error) {
+		if(error.hasErrors()) {
+			this.errors.addAll(error.getErrors());
+		}
+	}
+	
+	public void append(BigBlueButtonError error) {
+		errors.add(error);
+	}
+	
+	public boolean hasErrors() {
+		return !errors.isEmpty();
+	}
+	
+	public String getErrorMessages() {
+		StringBuilder sb = new StringBuilder(256);
+		for(BigBlueButtonError error:errors) {
+			if(sb.length() > 0) sb.append(", ");
+			sb.append(error.getCode() == null ? "UNKOWN" : error.getCode().name());
+		}
+		return sb.toString();
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonException.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonException.java
new file mode 100644
index 0000000000000000000000000000000000000000..017433467b4fc6408187a6d7de356b0537e111ad
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonException.java
@@ -0,0 +1,33 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.model;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonException extends Exception {
+
+	private static final long serialVersionUID = 3409095637259214708L;
+	public static final String SERVER_NOT_I18N_KEY = "error.notAvailable";
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf3266f32bb9c9535fda943f9a10e7dc990d4f55
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java
@@ -0,0 +1,325 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.core.id.Persistable;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupImpl;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="bigbluebuttonmeeting")
+@Table(name="o_bbb_meeting")
+public class BigBlueButtonMeetingImpl implements Persistable, BigBlueButtonMeeting {
+
+	private static final long serialVersionUID = -4319750358887231779L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="b_name", nullable=false, insertable=true, updatable=true)
+	private String name;
+	@Column(name="b_description", nullable=true, insertable=true, updatable=true)
+	private String description;
+	@Column(name="b_welcome", nullable=true, insertable=true, updatable=true)
+	private String welcome;
+	
+	@Column(name="b_start_date", nullable=true, insertable=true, updatable=true)
+	private Date startDate;
+	@Column(name="b_leadtime", nullable=true, insertable=true, updatable=true)
+	private long leadTime;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="b_start_with_leadtime", nullable=true, insertable=true, updatable=true)
+	private Date startWithLeadTime;
+	
+	@Column(name="b_end_date", nullable=true, insertable=true, updatable=true)
+	private Date endDate;
+	@Column(name="b_followuptime", nullable=true, insertable=true, updatable=true)
+	private long followupTime;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="b_end_with_followuptime", nullable=true, insertable=true, updatable=true)
+	private Date endWithFollowupTime;
+	
+	@Column(name="b_permanent", nullable=false, insertable=true, updatable=true)
+	private boolean permanent;
+
+	@Column(name="b_meeting_id", nullable=false, insertable=true, updatable=false)
+	private String meetingId;
+	@Column(name="b_attendee_pw", nullable=false, insertable=true, updatable=false)
+	private String attendeePassword;
+	@Column(name="b_moderator_pw", nullable=false, insertable=true, updatable=false)
+	private String moderatorPassword;
+	
+	@ManyToOne(targetEntity=RepositoryEntry.class, fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_entry_id", nullable=true, insertable=true, updatable=false)
+	private RepositoryEntry entry;
+	@Column(name="a_sub_ident", nullable=true, insertable=true, updatable=false)
+	private String subIdent;
+	
+	@ManyToOne(targetEntity=BusinessGroupImpl.class, fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_group_id", nullable=true, insertable=true, updatable=false)
+	private BusinessGroup businessGroup;
+	
+	@ManyToOne(targetEntity=BigBlueButtonMeetingTemplateImpl.class, fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_template_id", nullable=true, insertable=true, updatable=true)
+	private BigBlueButtonMeetingTemplate template;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	@Override
+	public String getMeetingId() {
+		return meetingId;
+	}
+
+	public void setMeetingId(String meetingId) {
+		this.meetingId = meetingId;
+	}
+
+	@Override
+	public String getAttendeePassword() {
+		return attendeePassword;
+	}
+
+	public void setAttendeePassword(String attendeePassword) {
+		this.attendeePassword = attendeePassword;
+	}
+
+	@Override
+	public String getModeratorPassword() {
+		return moderatorPassword;
+	}
+
+	public void setModeratorPassword(String moderatorPassword) {
+		this.moderatorPassword = moderatorPassword;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public void setDescription(String description) {
+		this.description = description;
+	}
+	
+	@Override
+	public String getWelcome() {
+		return welcome;
+	}
+
+	@Override
+	public void setWelcome(String welcome) {
+		this.welcome = welcome;
+	}
+
+	@Override
+	public boolean isPermanent() {
+		return permanent;
+	}
+
+	@Override
+	public void setPermanent(boolean permanent) {
+		this.permanent = permanent;
+	}
+
+	@Override
+	public Date getStartDate() {
+		return startDate;
+	}
+
+	@Override
+	public void setStartDate(Date start) {
+		this.startDate = start;
+	}
+
+	@Override
+	public long getLeadTime() {
+		return leadTime;
+	}
+
+	@Override
+	public void setLeadTime(long leadTime) {
+		this.leadTime = leadTime;
+	}
+
+	@Override
+	public Date getStartWithLeadTime() {
+		return startWithLeadTime;
+	}
+
+	public void setStartWithLeadTime(Date startWithLeadTime) {
+		this.startWithLeadTime = startWithLeadTime;
+	}
+
+	@Override
+	public Date getEndDate() {
+		return endDate;
+	}
+
+	@Override
+	public void setEndDate(Date end) {
+		this.endDate = end;
+	}
+
+	@Override
+	public long getFollowupTime() {
+		return followupTime;
+	}
+
+	@Override
+	public void setFollowupTime(long followupTime) {
+		this.followupTime = followupTime;
+	}
+
+	@Override
+	public Date getEndWithFollowupTime() {
+		return endWithFollowupTime;
+	}
+
+	public void setEndWithFollowupTime(Date endWithFollowupTime) {
+		this.endWithFollowupTime = endWithFollowupTime;
+	}
+
+	@Override
+	public RepositoryEntry getEntry() {
+		return entry;
+	}
+
+	public void setEntry(RepositoryEntry entry) {
+		this.entry = entry;
+	}
+
+	@Override
+	public String getSubIdent() {
+		return subIdent;
+	}
+
+	public void setSubIdent(String subIdent) {
+		this.subIdent = subIdent;
+	}
+
+	@Override
+	public BusinessGroup getBusinessGroup() {
+		return businessGroup;
+	}
+
+	public void setBusinessGroup(BusinessGroup businessGroup) {
+		this.businessGroup = businessGroup;
+	}
+	
+	@Override
+	public BigBlueButtonMeetingTemplate getTemplate() {
+		return template;
+	}
+
+	@Override
+	public void setTemplate(BigBlueButtonMeetingTemplate template) {
+		this.template = template;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 964210765 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof BigBlueButtonMeetingImpl) {
+			BigBlueButtonMeetingImpl meeting = (BigBlueButtonMeetingImpl)obj;
+			return getKey() != null && getKey().equals(meeting.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c97189530b1c24b220f9e8af36d746d7e79bf29
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java
@@ -0,0 +1,332 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.core.id.Persistable;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.modules.bigbluebutton.GuestPolicyEnum;
+
+/**
+ * 
+ * Initial date: 19 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="bigbluebuttontemplate")
+@Table(name="o_bbb_template")
+public class BigBlueButtonMeetingTemplateImpl implements Persistable, BigBlueButtonMeetingTemplate {
+
+	private static final long serialVersionUID = 5348654669384123670L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="b_name", nullable=false, insertable=true, updatable=true)
+	private String name;
+	@Column(name="b_description", nullable=true, insertable=true, updatable=true)
+	private String description;
+	@Column(name="b_system", nullable=false, insertable=true, updatable=false)
+	private boolean system;
+	@Column(name="b_external_id", nullable=true, insertable=true, updatable=true)
+	private String externalId;
+	
+	@Column(name="b_max_participants", nullable=true, insertable=true, updatable=true)
+	private Integer maxParticipants;
+
+	@Column(name="b_mute_on_start", nullable=true, insertable=true, updatable=true)
+	private Boolean muteOnStart;
+	@Column(name="b_auto_start_recording", nullable=true, insertable=true, updatable=true)
+	private Boolean autoStartRecording;
+	@Column(name="b_allow_start_stop_recording", nullable=true, insertable=true, updatable=true)
+	private Boolean allowStartStopRecording;
+	@Column(name="b_webcams_only_for_moderator", nullable=true, insertable=true, updatable=true)
+	private Boolean webcamsOnlyForModerator;
+	@Column(name="b_allow_mods_to_unmute_users", nullable=true, insertable=true, updatable=true)
+	private Boolean allowModsToUnmuteUsers;
+	
+	@Column(name="b_lock_set_disable_cam", nullable=true, insertable=true, updatable=true)
+	private Boolean lockSettingsDisableCam;
+	@Column(name="b_lock_set_disable_mic", nullable=true, insertable=true, updatable=true)
+	private Boolean lockSettingsDisableMic;
+	@Column(name="b_lock_set_disable_priv_chat", nullable=true, insertable=true, updatable=true)
+	private Boolean lockSettingsDisablePrivateChat;
+	@Column(name="b_lock_set_disable_public_chat", nullable=true, insertable=true, updatable=true)
+	private Boolean lockSettingsDisablePublicChat;
+	@Column(name="b_lock_set_disable_note", nullable=true, insertable=true, updatable=true)
+	private Boolean lockSettingsDisableNote;
+	@Column(name="b_lock_set_locked_layout", nullable=true, insertable=true, updatable=true)
+	private Boolean lockSettingsLockedLayout;
+	
+	@Column(name="b_guest_policy", nullable=true, insertable=true, updatable=true)
+	private String guestPolicy;
+	
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	@Override
+	public boolean isSystem() {
+		return system;
+	}
+
+	public void setSystem(boolean system) {
+		this.system = system;
+	}
+
+	@Override
+	public String getExternalId() {
+		return externalId;
+	}
+
+	public void setExternalId(String externalId) {
+		this.externalId = externalId;
+	}
+
+	@Override
+	public Integer getMaxParticipants() {
+		return maxParticipants;
+	}
+
+	@Override
+	public void setMaxParticipants(Integer maxParticipants) {
+		this.maxParticipants = maxParticipants;
+	}
+
+	@Override
+	public Boolean getMuteOnStart() {
+		return muteOnStart;
+	}
+
+	@Override
+	public void setMuteOnStart(Boolean muteOnStart) {
+		this.muteOnStart = muteOnStart;
+	}
+
+	@Override
+	public Boolean getAutoStartRecording() {
+		return autoStartRecording;
+	}
+
+	@Override
+	public void setAutoStartRecording(Boolean autoStartRecording) {
+		this.autoStartRecording = autoStartRecording;
+	}
+
+	@Override
+	public Boolean getAllowStartStopRecording() {
+		return allowStartStopRecording;
+	}
+
+	@Override
+	public void setAllowStartStopRecording(Boolean allowStartStopRecording) {
+		this.allowStartStopRecording = allowStartStopRecording;
+	}
+
+	@Override
+	public Boolean getWebcamsOnlyForModerator() {
+		return webcamsOnlyForModerator;
+	}
+
+	@Override
+	public void setWebcamsOnlyForModerator(Boolean webcamsOnlyForModerator) {
+		this.webcamsOnlyForModerator = webcamsOnlyForModerator;
+	}
+
+	@Override
+	public Boolean getAllowModsToUnmuteUsers() {
+		return allowModsToUnmuteUsers;
+	}
+
+	@Override
+	public void setAllowModsToUnmuteUsers(Boolean allowModsToUnmuteUsers) {
+		this.allowModsToUnmuteUsers = allowModsToUnmuteUsers;
+	}
+
+	@Override
+	public Boolean getLockSettingsDisableCam() {
+		return lockSettingsDisableCam;
+	}
+
+	@Override
+	public void setLockSettingsDisableCam(Boolean lockSettingsDisableCam) {
+		this.lockSettingsDisableCam = lockSettingsDisableCam;
+	}
+
+	@Override
+	public Boolean getLockSettingsDisableMic() {
+		return lockSettingsDisableMic;
+	}
+
+	@Override
+	public void setLockSettingsDisableMic(Boolean lockSettingsDisableMic) {
+		this.lockSettingsDisableMic = lockSettingsDisableMic;
+	}
+
+	@Override
+	public Boolean getLockSettingsDisablePrivateChat() {
+		return lockSettingsDisablePrivateChat;
+	}
+
+	@Override
+	public void setLockSettingsDisablePrivateChat(Boolean lockSettingsDisablePrivateChat) {
+		this.lockSettingsDisablePrivateChat = lockSettingsDisablePrivateChat;
+	}
+
+	@Override
+	public Boolean getLockSettingsDisablePublicChat() {
+		return lockSettingsDisablePublicChat;
+	}
+
+	@Override
+	public void setLockSettingsDisablePublicChat(Boolean lockSettingsDisablePublicChat) {
+		this.lockSettingsDisablePublicChat = lockSettingsDisablePublicChat;
+	}
+
+	@Override
+	public Boolean getLockSettingsDisableNote() {
+		return lockSettingsDisableNote;
+	}
+
+	@Override
+	public void setLockSettingsDisableNote(Boolean lockSettingsDisableNote) {
+		this.lockSettingsDisableNote = lockSettingsDisableNote;
+	}
+
+	@Override
+	public Boolean getLockSettingsLockedLayout() {
+		return lockSettingsLockedLayout;
+	}
+
+	@Override
+	public void setLockSettingsLockedLayout(Boolean lockSettingsLockedLayout) {
+		this.lockSettingsLockedLayout = lockSettingsLockedLayout;
+	}
+
+	public String getGuestPolicy() {
+		return guestPolicy;
+	}
+
+	public void setGuestPolicy(String guestPolicy) {
+		this.guestPolicy = guestPolicy;
+	}
+	
+	public GuestPolicyEnum getGuestPolicyEnum() {
+		if(StringHelper.containsNonWhitespace(guestPolicy)) {
+			return GuestPolicyEnum.valueOf(guestPolicy);
+		}
+		return GuestPolicyEnum.ALWAYS_DENY;
+	}
+
+	public void setGuestPolicyEnum(GuestPolicyEnum guestPolicy) {
+		if(guestPolicy == null) {
+			this.guestPolicy = GuestPolicyEnum.ALWAYS_DENY.name();
+		} else {
+			this.guestPolicy = guestPolicy.name();
+		}
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 964210765 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof BigBlueButtonMeetingImpl) {
+			BigBlueButtonMeetingImpl meeting = (BigBlueButtonMeetingImpl)obj;
+			return getKey() != null && getKey().equals(meeting.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java
new file mode 100644
index 0000000000000000000000000000000000000000..868fe75fa4909f1cce432c63e5fdf4ebd3510835
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java
@@ -0,0 +1,148 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.components.segmentedview.SegmentViewComponent;
+import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
+import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.core.gui.control.generic.dtabs.Activateable2;
+import org.olat.core.id.context.ContextEntry;
+import org.olat.core.id.context.StateEntry;
+import org.olat.core.util.resource.OresHelper;
+
+/**
+ * 
+ * Initial date: 28 févr. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonAdminController extends BasicController implements Activateable2 {
+	
+	private final Link meetingsLink;
+	private final Link templatesLink;
+	private final Link configurationLink;
+	private final SegmentViewComponent segmentView;
+	private final VelocityContainer mainVC;
+	
+	private BigBlueButtonConfigurationController configCtrl;
+	private BigBlueButtonAdminMeetingsController meetingsCtrl;
+	private BigBlueButtonAdminTemplatesController templatesCtrl;
+	
+	public BigBlueButtonAdminController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		mainVC = createVelocityContainer("bbb_admin");
+		
+		segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+		configurationLink = LinkFactory.createLink("account.configuration", mainVC, this);
+		segmentView.addSegment(configurationLink, true);
+		templatesLink = LinkFactory.createLink("templates.title", mainVC, this);
+		segmentView.addSegment(templatesLink, false);
+		meetingsLink = LinkFactory.createLink("meetings.title", mainVC, this);
+		segmentView.addSegment(meetingsLink, false);
+		
+		doOpenConfiguration(ureq);
+		
+		putInitialPanel(mainVC);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
+		if(entries == null || entries.isEmpty()) return;
+
+		String type = entries.get(0).getOLATResourceable().getResourceableTypeName();
+		if("Configuration".equalsIgnoreCase(type)) {
+			doOpenConfiguration(ureq);
+			segmentView.select(configurationLink);
+		} else if("Templates".equalsIgnoreCase(type)) {
+			doOpenTemplates(ureq);
+			segmentView.select(templatesLink);
+		} else if("Meetings".equalsIgnoreCase(type)) {
+			doOpenMeetings(ureq);
+			segmentView.select(meetingsLink);
+		}
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(source == segmentView) {
+			if(event instanceof SegmentViewEvent) {
+				SegmentViewEvent sve = (SegmentViewEvent)event;
+				String segmentCName = sve.getComponentName();
+				Component clickedLink = mainVC.getComponent(segmentCName);
+				if (clickedLink == configurationLink) {
+					doOpenConfiguration(ureq);
+				} else if (clickedLink == templatesLink){
+					doOpenTemplates(ureq);
+				} else if (clickedLink == meetingsLink){
+					doOpenMeetings(ureq);
+				}
+			}
+		}
+	}
+	
+	private void doOpenConfiguration(UserRequest ureq) {
+		if(configCtrl == null) {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Configuration", 0l), null);
+			configCtrl = new BigBlueButtonConfigurationController(ureq, bwControl);
+			listenTo(configCtrl);
+		} else {
+			addToHistory(ureq, configCtrl);
+		}
+		mainVC.put("segmentCmp", configCtrl.getInitialComponent());
+	}
+	
+	private void doOpenTemplates(UserRequest ureq) {
+		if(templatesCtrl == null) {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Templates", 0l), null);
+			templatesCtrl = new BigBlueButtonAdminTemplatesController(ureq, bwControl);
+			listenTo(templatesCtrl);
+		} else {
+			addToHistory(ureq, templatesCtrl);
+		}
+		mainVC.put("segmentCmp", templatesCtrl.getInitialComponent());
+	}
+	
+	private void doOpenMeetings(UserRequest ureq) {
+		if(meetingsCtrl == null) {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Meetings", 0l), null);
+			meetingsCtrl = new BigBlueButtonAdminMeetingsController(ureq, bwControl);
+			listenTo(meetingsCtrl);
+		} else {
+			addToHistory(ureq, meetingsCtrl);
+		}
+		mainVC.put("segmentCmp", meetingsCtrl.getInitialComponent());
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminMeetingsController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminMeetingsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..071ff0065b44c956c6f669c65cf6f1d45e43febd
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminMeetingsController.java
@@ -0,0 +1,173 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+
+import org.olat.NewControllerFactory;
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableSortOptions;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonMeetingTableModel.BMeetingsCols;
+import org.olat.modules.gotomeeting.ui.GoToMeetingTableModel.MeetingsCols;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonAdminMeetingsController extends FormBasicController {
+	
+	private FlexiTableElement tableEl;
+	private BigBlueButtonMeetingTableModel tableModel;
+
+	private DialogBoxController confirmDelete;
+	
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+
+	public BigBlueButtonAdminMeetingsController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, "meetings_admin");
+		
+		initForm(ureq);
+		updateModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.name));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.permanent));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.start));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.end));
+		FlexiCellRenderer renderer = new StaticFlexiCellRenderer("resource", new TextFlexiCellRenderer());
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.resource.i18nHeaderKey(), BMeetingsCols.resource.ordinal(), "resource",
+				true, BMeetingsCols.resource.name(), renderer));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("delete", translate("delete"), "delete"));
+
+		tableModel = new BigBlueButtonMeetingTableModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "meetings", tableModel, getTranslator(), formLayout);
+		
+		FlexiTableSortOptions sortOptions = new FlexiTableSortOptions();
+		sortOptions.setDefaultOrderBy(new SortKey(MeetingsCols.start.name(), false));
+		tableEl.setSortSettings(sortOptions);
+		tableEl.setAndLoadPersistedPreferences(ureq, "bigbluebutton-admin-meetings-list");
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	private void updateModel() {
+		List<BigBlueButtonMeeting> meetings = bigBlueButtonManager.getAllMeetings();
+		tableModel.setObjects(meetings);
+		tableEl.reset(true, true, true);	
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(confirmDelete == source) {
+			if(DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
+				BigBlueButtonMeeting meeting = (BigBlueButtonMeeting)confirmDelete.getUserObject();
+				doDelete(meeting);
+			}
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("resource".equals(se.getCommand())) {
+					doOpenResource(ureq, tableModel.getObject(se.getIndex()));
+				} else if("delete".equals(se.getCommand())) {
+					doConfirmDelete(ureq, tableModel.getObject(se.getIndex()));
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(confirmDelete);
+		confirmDelete = null;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	private void doOpenResource(UserRequest ureq, BigBlueButtonMeeting meeting) {
+		if(meeting.getEntry() != null) {
+			String businessPath = "[RepositoryEntry:" + meeting.getEntry().getKey() + "]";
+			if(StringHelper.containsNonWhitespace(meeting.getSubIdent())) {
+				businessPath += "[CourseNode:" + meeting.getSubIdent() + "]";
+			}
+			NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
+		} else if(meeting.getBusinessGroup() != null) {
+			String businessPath = "[BusinessGroup:" + meeting.getBusinessGroup().getKey() + "]";
+			NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
+		}
+	}
+	
+	private void doConfirmDelete(UserRequest ureq, BigBlueButtonMeeting meeting) {
+		String confirmDeleteTitle = translate("confirm.delete.meeting.title", new String[]{ meeting.getName() });
+		String confirmDeleteText = translate("confirm.delete.meeting", new String[]{ meeting.getName() });
+		confirmDelete = activateYesNoDialog(ureq, confirmDeleteTitle, confirmDeleteText, confirmDelete);
+		confirmDelete.setUserObject(meeting);
+	}
+	
+	private void doDelete(BigBlueButtonMeeting meeting) {
+		BigBlueButtonErrors errors = new BigBlueButtonErrors();
+		bigBlueButtonManager.deleteMeeting(meeting, errors);
+		updateModel();
+		if(errors.hasErrors()) {
+			getWindowControl().setError(BigBlueButtonErrorHelper.formatErrors(getTranslator(), errors));
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f1b083e3bc99bb87d60f6ccdc5a84847958697c
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java
@@ -0,0 +1,196 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.BooleanCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonTemplateTableModel.BTemplatesCols;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonAdminTemplatesController extends FormBasicController {
+	
+	private FlexiTableElement tableEl;
+	private FormLink addTemplateButton;
+	private BigBlueButtonTemplateTableModel tableModel;
+	
+	private CloseableModalController cmc;
+	private DialogBoxController confirmDelete;
+	private EditBigBlueButtonTemplateController editTemplateCtlr;
+	
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public BigBlueButtonAdminTemplatesController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, "templates_admin");
+		initForm(ureq);
+		updateModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		addTemplateButton = uifactory.addFormLink("add.template", formLayout, Link.BUTTON);
+		
+		//add the table
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BTemplatesCols.name));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BTemplatesCols.system));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("edit", translate("edit"), "edit"));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("delete", BTemplatesCols.system.ordinal(), "delete",
+				new BooleanCellRenderer(null, new StaticFlexiCellRenderer(translate("delete"), "delete"))));
+		
+		tableModel = new BigBlueButtonTemplateTableModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "templates", tableModel, getTranslator(), formLayout);
+		tableEl.setEmtpyTableMessageKey("no.template.configured");
+		tableEl.setAndLoadPersistedPreferences(ureq, "bigbluebutton-connect-edit-templates-list");
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	public void updateModel() {
+		List<BigBlueButtonMeetingTemplate> templates = bigBlueButtonManager.getTemplates();
+		tableModel.setObjects(templates);
+		tableEl.reset(true, true, true);	
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(source == editTemplateCtlr) {
+			if(event == Event.DONE_EVENT) {
+				updateModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(confirmDelete == source) {
+			if(DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
+				BigBlueButtonMeetingTemplate meeting = (BigBlueButtonMeetingTemplate)confirmDelete.getUserObject();
+				doDelete(meeting);
+			}
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(editTemplateCtlr);
+		removeAsListenerAndDispose(confirmDelete);
+		removeAsListenerAndDispose(cmc);
+		editTemplateCtlr = null;
+		confirmDelete = null;
+		cmc = null;
+	}
+	
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(addTemplateButton == source) {
+			doAddTemplate(ureq);
+		} else if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("edit".equals(se.getCommand())) {
+					doEditTemplate(ureq, tableModel.getObject(se.getIndex()));
+				} else if("delete".equals(se.getCommand())) {
+					doConfirmDelete(ureq, tableModel.getObject(se.getIndex()));
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+
+	private void doAddTemplate(UserRequest ureq) {
+		if(guardModalController(editTemplateCtlr)) return;
+		
+		editTemplateCtlr = new EditBigBlueButtonTemplateController(ureq, getWindowControl());
+		listenTo(editTemplateCtlr);
+		
+		cmc = new CloseableModalController(getWindowControl(), "close", editTemplateCtlr.getInitialComponent(),
+				true, translate("add.template"));
+		cmc.activate();
+		listenTo(cmc);
+	}
+	
+	private void doEditTemplate(UserRequest ureq, BigBlueButtonMeetingTemplate template) {
+		if(guardModalController(editTemplateCtlr)) return;
+		
+		editTemplateCtlr = new EditBigBlueButtonTemplateController(ureq, getWindowControl(), template);
+		listenTo(editTemplateCtlr);
+		
+		cmc = new CloseableModalController(getWindowControl(), "close", editTemplateCtlr.getInitialComponent(),
+				true, translate("edit.template", new String[] { template.getName() }));
+		cmc.activate();
+		listenTo(cmc);
+	}
+	
+	private void doConfirmDelete(UserRequest ureq, BigBlueButtonMeetingTemplate template) {
+		if(bigBlueButtonManager.isTemplateInUse(template)) {
+			showWarning("warning.template.in.use");
+		} else {
+			String confirmDeleteTitle = translate("confirm.delete.template.title", new String[]{ template.getName() });
+			String confirmDeleteText = translate("confirm.delete.template", new String[]{ template.getName() });
+			confirmDelete = activateYesNoDialog(ureq, confirmDeleteTitle, confirmDeleteText, confirmDelete);
+			confirmDelete.setUserObject(template);
+		}
+	}
+	
+	private void doDelete(BigBlueButtonMeetingTemplate template) {
+		if(!bigBlueButtonManager.isTemplateInUse(template)) {
+			bigBlueButtonManager.deleteTemplate(template);
+			updateModel();
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java
new file mode 100644
index 0000000000000000000000000000000000000000..122d4c104e8ce8223b2906e11c2af930944650b4
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java
@@ -0,0 +1,295 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+
+import org.olat.collaboration.CollaborationToolsFactory;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.elements.SpacerElement;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonModule;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonConfigurationController extends FormBasicController {
+
+	private static final String[] CLEAN_KEYS = { "-", "1", "2", "3", "4", "5", "7", "14", "21", "30" };
+	private static final String[] FOR_KEYS = { "courses", "groups" };
+	private static final String PLACEHOLDER = "xxx-placeholder-xxx";
+	
+	private FormLink checkLink;
+	private TextElement urlEl;
+	private SpacerElement spacerEl;
+	private TextElement sharedSecretEl;
+	private SingleSelection cleanMeetingsEl;
+	private MultipleSelectionElement moduleEnabled;
+	private MultipleSelectionElement enabledForEl;
+
+	private static final String[] enabledKeys = new String[]{"on"};
+	private final String[] enabledValues;
+	
+	private String replacedSharedSecretValue;
+	
+	@Autowired
+	private BigBlueButtonModule bigBlueButtonModule;
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public BigBlueButtonConfigurationController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		enabledValues = new String[]{translate("enabled")};
+		initForm(ureq);
+		updateUI();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormTitle("bigbluebutton.title");
+		setFormInfo("bigbluebutton.intro");
+		setFormContextHelp("Communication and Collaboration#_bigbluebutton_config");
+		
+		moduleEnabled = uifactory.addCheckboxesHorizontal("bigbluebutton.module.enabled", formLayout, enabledKeys, enabledValues);
+		moduleEnabled.select(enabledKeys[0], bigBlueButtonModule.isEnabled());
+		moduleEnabled.addActionListener(FormEvent.ONCHANGE);
+		
+		String[] forValues = new String[] {
+			translate("bigbluebutton.module.enabled.for.courses"), translate("bigbluebutton.module.enabled.for.groups")
+		};
+		enabledForEl = uifactory.addCheckboxesVertical("bigbluebutton.module.enabled.for", formLayout, FOR_KEYS, forValues, 1);
+		enabledForEl.select(FOR_KEYS[0], bigBlueButtonModule.isCoursesEnabled());
+		enabledForEl.select(FOR_KEYS[1], bigBlueButtonModule.isGroupsEnabled());
+			
+		//spacer
+		spacerEl = uifactory.addSpacerElement("spacer", formLayout, false);
+
+		URI uri = bigBlueButtonModule.getBigBlueButtonURI();
+		String uriStr = uri == null ? "" : uri.toString();
+		urlEl = uifactory.addTextElement("bbb-url", "option.baseurl", 255, uriStr, formLayout);
+		urlEl.setDisplaySize(60);
+		urlEl.setExampleKey("option.baseurl.example", null);
+
+		String sharedSecret = bigBlueButtonModule.getSharedSecret();
+		if(StringHelper.containsNonWhitespace(sharedSecret)) {
+			replacedSharedSecretValue = sharedSecret;
+			sharedSecret = PLACEHOLDER;
+		}
+		sharedSecretEl = uifactory.addPasswordElement("shared.secret", "option.bigbluebutton.shared.secret", 255, sharedSecret, formLayout);
+		sharedSecretEl.setAutocomplete("new-password");
+		
+		// delete meeting
+		String[] cleanValues = Arrays.copyOf(CLEAN_KEYS, CLEAN_KEYS.length);
+		cleanValues[0] = translate("option.dont.clean.meetings");
+		cleanMeetingsEl = uifactory.addDropdownSingleselect("option.clean.meetings", formLayout, CLEAN_KEYS, cleanValues);
+		if(bigBlueButtonModule.isCleanupMeetings()) {
+			long days = bigBlueButtonModule.getDaysToKeep();
+			String dayStr = Long.toString(days);
+			for(String key:CLEAN_KEYS) {
+				if(dayStr.equals(key)) {
+					cleanMeetingsEl.select(key, true);
+				}
+			}
+		} else {
+			cleanMeetingsEl.select(CLEAN_KEYS[0], true);
+		}
+		
+		//buttons save - check
+		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("save", getTranslator());
+		formLayout.add(buttonLayout);
+		uifactory.addFormSubmitButton("save", buttonLayout);
+		checkLink = uifactory.addFormLink("check", buttonLayout, Link.BUTTON);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	private void updateUI() {
+		boolean enabled = moduleEnabled.isAtLeastSelected(1);
+		enabledForEl.setVisible(enabled);
+		checkLink.setVisible(enabled);
+		urlEl.setVisible(enabled);
+		sharedSecretEl.setVisible(enabled);
+		spacerEl.setVisible(enabled);
+		cleanMeetingsEl.setVisible(enabled);
+	}
+	
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		//validate only if the module is enabled
+		if(moduleEnabled.isAtLeastSelected(1)) {
+			allOk &= validateUrlFields();
+			if(allOk) {
+				allOk &= validateConnection();
+			}
+		}
+		
+		return allOk;
+	}
+
+	private boolean validateUrlFields() {
+		boolean allOk = true;
+		
+		String url = urlEl.getValue();
+		urlEl.clearError();
+		if(StringHelper.containsNonWhitespace(url)) {
+			try {
+				URI uri = new URI(url);
+				uri.getHost();
+			} catch(Exception e) {
+				urlEl.setErrorKey("error.url.invalid", null);
+				allOk &= false;
+			}
+		} else {
+			urlEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		String password = sharedSecretEl.getValue();
+		sharedSecretEl.clearError();
+		if(!StringHelper.containsNonWhitespace(password)) {
+			sharedSecretEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		return allOk;
+	}
+	
+	private boolean validateConnection() {
+		boolean allOk = true;
+		try {
+			BigBlueButtonErrors errors = new BigBlueButtonErrors();
+			boolean ok = checkConnection(errors);
+			if(!ok || errors.hasErrors()) {
+				sharedSecretEl.setValue("");
+				urlEl.setErrorKey("error.customerDoesntExist", null);
+				allOk &= false;
+			}
+		} catch (Exception e) {
+			showError(BigBlueButtonException.SERVER_NOT_I18N_KEY);
+			allOk &= false;
+		}
+		return allOk;
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(source == moduleEnabled) {
+			updateUI();
+		} else if(source == checkLink) {
+			if(validateUrlFields()) {
+				doCheckConnection();
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+	@Override
+	protected void formOK(UserRequest ureq) {
+		try {
+			boolean enabled = moduleEnabled.isSelected(0);
+			bigBlueButtonModule.setEnabled(enabled);
+			// update collaboration tools list
+			if(enabled) {
+				String url = urlEl.getValue();
+				bigBlueButtonModule.setBigBlueButtonURI(new URI(url));
+				bigBlueButtonModule.setCoursesEnabled(enabledForEl.isSelected(0));
+				bigBlueButtonModule.setGroupsEnabled(enabledForEl.isSelected(1));
+				if(cleanMeetingsEl.isSelected(0)) {
+					bigBlueButtonModule.setCleanupMeetings(false);
+					bigBlueButtonModule.setDaysToKeep(null);
+				} else {
+					bigBlueButtonModule.setCleanupMeetings(true);
+					bigBlueButtonModule.setDaysToKeep(cleanMeetingsEl.getSelectedKey());
+				}
+				
+				String sharedSecret = sharedSecretEl.getValue();
+				if(!PLACEHOLDER.equals(sharedSecret)) {
+					bigBlueButtonModule.setSharedSecret(sharedSecret);
+					sharedSecretEl.setValue(PLACEHOLDER);
+				} else if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) {
+					bigBlueButtonModule.setSharedSecret(replacedSharedSecretValue);
+				}
+			} else {
+				bigBlueButtonModule.setBigBlueButtonURI(null);
+				bigBlueButtonModule.setSecret(null);
+				bigBlueButtonModule.setSharedSecret(null);
+			}
+			CollaborationToolsFactory.getInstance().initAvailableTools();
+		} catch (URISyntaxException e) {
+			logError("", e);
+			urlEl.setErrorKey("error.url.invalid", null);
+		}
+	}
+	
+	private void doCheckConnection() {
+		BigBlueButtonErrors errors = new BigBlueButtonErrors();
+		boolean loginOk = checkConnection(errors);
+		if(errors.hasErrors()) {
+			getWindowControl().setError(BigBlueButtonErrorHelper.formatErrors(getTranslator(), errors));
+		} else if(loginOk) {
+			showInfo("connection.successful");
+		} else {
+			showError("connection.failed");
+		}
+	}
+	
+	private boolean checkConnection(BigBlueButtonErrors errors) {
+		String url = urlEl.getValue();
+		String sharedSecret = sharedSecretEl.getValue();
+		if(PLACEHOLDER.equals(sharedSecret)) {
+			if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) {
+				sharedSecret = replacedSharedSecretValue;
+			} else {
+				sharedSecret = bigBlueButtonModule.getSharedSecret();
+			}
+		} else {
+			replacedSharedSecretValue = sharedSecret;
+			sharedSecretEl.setValue(PLACEHOLDER);
+		}
+		
+		return bigBlueButtonManager.checkConnection(url, sharedSecret, errors);
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonEditMeetingsController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonEditMeetingsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..f75a5486f1a92089a3a47716397254ea3293bc88
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonEditMeetingsController.java
@@ -0,0 +1,219 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableSortOptions;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonMeetingTableModel.BMeetingsCols;
+import org.olat.modules.gotomeeting.ui.GoToMeetingTableModel.MeetingsCols;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonEditMeetingsController extends FormBasicController {
+	
+	private FlexiTableElement tableEl;
+	private FormLink addMeetingButton;
+	private BigBlueButtonMeetingTableModel tableModel;
+	
+	private CloseableModalController cmc;
+	private DialogBoxController confirmDelete;
+	private EditBigBlueButtonMeetingController editMeetingCtlr;
+	
+	private final boolean readOnly;
+	private final String subIdent;
+	private final RepositoryEntry entry;
+	private final BusinessGroup businessGroup;
+	
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public BigBlueButtonEditMeetingsController(UserRequest ureq, WindowControl wControl,
+			RepositoryEntry entry, String subIdentifier, BusinessGroup group, boolean readOnly) {
+		super(ureq, wControl, "meetings_admin");
+		this.readOnly = readOnly;
+		this.entry = entry;
+		this.subIdent = subIdentifier;
+		this.businessGroup = group;
+		initForm(ureq);
+		updateModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(!readOnly) {
+			addMeetingButton = uifactory.addFormLink("add.meeting", formLayout, Link.BUTTON);
+		}
+		
+		//add the table
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.name));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.permanent));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.start));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.end));
+		if(!readOnly) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("edit", translate("edit"), "edit"));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("delete", translate("delete"), "delete"));
+		}
+		
+		tableModel = new BigBlueButtonMeetingTableModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "meetings", tableModel, getTranslator(), formLayout);
+		tableEl.setEmtpyTableMessageKey("no.meeting.configured");
+		
+		FlexiTableSortOptions sortOptions = new FlexiTableSortOptions();
+		sortOptions.setDefaultOrderBy(new SortKey(MeetingsCols.start.name(), false));
+		tableEl.setSortSettings(sortOptions);
+		tableEl.setAndLoadPersistedPreferences(ureq, "bigbluebutton-connect-edit-meetings-list");
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	public void updateModel() {
+		List<BigBlueButtonMeeting> meetings = bigBlueButtonManager.getMeetings(entry, subIdent, businessGroup);
+		tableModel.setObjects(meetings);
+		tableEl.reset(true, true, true);	
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(source == editMeetingCtlr) {
+			if(event == Event.DONE_EVENT) {
+				updateModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(confirmDelete == source) {
+			if(DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
+				BigBlueButtonMeeting meeting = (BigBlueButtonMeeting)confirmDelete.getUserObject();
+				doDelete(meeting);
+			}
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(editMeetingCtlr);
+		removeAsListenerAndDispose(confirmDelete);
+		removeAsListenerAndDispose(cmc);
+		editMeetingCtlr = null;
+		confirmDelete = null;
+		cmc = null;
+	}
+	
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(addMeetingButton == source) {
+			doAddMeeting(ureq);
+		} else if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("edit".equals(se.getCommand())) {
+					doEditMeeting(ureq, tableModel.getObject(se.getIndex()));
+				} else if("delete".equals(se.getCommand())) {
+					doConfirmDelete(ureq, tableModel.getObject(se.getIndex()));
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+
+	private void doAddMeeting(UserRequest ureq) {
+		if(guardModalController(editMeetingCtlr)) return;
+		
+		editMeetingCtlr = new EditBigBlueButtonMeetingController(ureq, getWindowControl(), entry, subIdent, businessGroup);
+		listenTo(editMeetingCtlr);
+		
+		cmc = new CloseableModalController(getWindowControl(), "close", editMeetingCtlr.getInitialComponent(),
+				true, translate("add.meeting"));
+		cmc.activate();
+		listenTo(cmc);
+	}
+	
+	private void doEditMeeting(UserRequest ureq, BigBlueButtonMeeting meeting) {
+		if(guardModalController(editMeetingCtlr)) return;
+		
+		editMeetingCtlr = new EditBigBlueButtonMeetingController(ureq, getWindowControl(), meeting);
+		listenTo(editMeetingCtlr);
+		
+		cmc = new CloseableModalController(getWindowControl(), "close", editMeetingCtlr.getInitialComponent(),
+				true, translate("add.meeting"));
+		cmc.activate();
+		listenTo(cmc);
+	}
+	
+	private void doConfirmDelete(UserRequest ureq, BigBlueButtonMeeting meeting) {
+		String confirmDeleteTitle = translate("confirm.delete.meeting.title", new String[]{ meeting.getName() });
+		String confirmDeleteText = translate("confirm.delete.meeting", new String[]{ meeting.getName() });
+		confirmDelete = activateYesNoDialog(ureq, confirmDeleteTitle, confirmDeleteText, confirmDelete);
+		confirmDelete.setUserObject(meeting);
+	}
+	
+	private void doDelete(BigBlueButtonMeeting meeting) {
+		BigBlueButtonErrors errors = new BigBlueButtonErrors();
+		bigBlueButtonManager.deleteMeeting(meeting, errors);
+		updateModel();
+		if(errors.hasErrors()) {
+			getWindowControl().setError(BigBlueButtonErrorHelper.formatErrors(getTranslator(), errors));
+		} else {
+			showInfo("meeting.deleted");
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonErrorHelper.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonErrorHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed6860390e4a6fb3b8f399a8b0d4cdcc491725c1
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonErrorHelper.java
@@ -0,0 +1,50 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonError;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+
+/**
+ * 
+ * Initial date: 23 avr. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonErrorHelper {
+	
+	public static final String formatErrors(Translator translator, BigBlueButtonErrors errors) {
+		StringBuilder sb = new StringBuilder(1024);
+		sb.append(translator.translate("error.prefix"))
+		  .append("<ul>");
+		for(BigBlueButtonError error:errors.getErrors()) {
+			sb.append("<li>");
+			if(StringHelper.containsNonWhitespace(error.getMessageKey())) {
+				sb.append(translator.translate("error.server.raw", new String[]{ error.getMessageKey(), error.getMessage() } ));
+			} else {
+				sb.append(translator.translate("error." + error.getCode().name(), error.getArguments()));
+			}
+			sb.append("</li>");
+		}
+		return sb.append("</ul>").toString();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonEvent.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0ea3fd0e88213efb4cf6c867bf3ff96836c0f21
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonEvent.java
@@ -0,0 +1,51 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import org.olat.core.util.event.MultiUserEvent;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonEvent extends MultiUserEvent {
+
+	private static final long serialVersionUID = 5967661384792368873L;
+	public static final String OPEN_MEETING = "bigbluebutton-open-meeting";
+	
+	private final Long meetingKey;
+	private final Long actingIdentityKey;
+	
+	public BigBlueButtonEvent(Long meetingKey, Long actingIdentityKey) {
+		super(OPEN_MEETING);
+		this.meetingKey = meetingKey;
+		this.actingIdentityKey = actingIdentityKey;
+	}
+
+	public Long getMeetingKey() {
+		return meetingKey;
+	}
+
+	public Long getActingIdentityKey() {
+		return actingIdentityKey;
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ed37f2d1056e5afb08689df2bc02fcee1ffc8ee
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
@@ -0,0 +1,231 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.Date;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.media.RedirectMediaResource;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.util.Formatter;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.core.util.event.GenericEventListener;
+import org.olat.core.util.resource.OresHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 4 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonMeetingController extends FormBasicController implements GenericEventListener {
+	
+	private final boolean readOnly;
+	private final boolean moderator;
+	private final boolean administrator;
+	private BigBlueButtonMeeting meeting;
+	
+	private final boolean userMeetingsDates;
+	private final boolean moderatorStartMeeting;
+	private final OLATResourceable meetingOres;
+
+	private Link joinButton;
+
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public BigBlueButtonMeetingController(UserRequest ureq, WindowControl wControl,
+			BigBlueButtonMeeting meeting, BigBlueButtonMeetingDefaultConfiguration configuration,
+			boolean administrator, boolean moderator, boolean readOnly) {
+		super(ureq, wControl, "meeting");
+		this.meeting = meeting;
+		this.readOnly = readOnly;
+		this.moderator = moderator;
+		this.administrator = administrator;
+		meetingOres = OresHelper.createOLATResourceableInstance(BigBlueButtonMeeting.class.getSimpleName(), meeting.getKey());
+		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, getIdentity(), meetingOres);
+
+		userMeetingsDates = !meeting.isPermanent();
+		moderatorStartMeeting = configuration.isModeratorStartMeeting();
+		
+		initForm(ureq);
+
+		updateButtonsAndStatus();
+	}
+	
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		boolean ended = isEnded();
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			layoutCont.contextPut("title", meeting.getName());
+			if(StringHelper.containsNonWhitespace(meeting.getDescription())) {
+				layoutCont.contextPut("description", meeting.getDescription());
+			}
+			if(meeting.getStartDate() != null) {
+				String start = Formatter.getInstance(getLocale()).formatDateAndTime(meeting.getStartDate());
+				layoutCont.contextPut("start", start);
+			}
+			if(meeting.getEndDate() != null) {
+				String end = Formatter.getInstance(getLocale()).formatDateAndTime(meeting.getEndDate());
+				layoutCont.contextPut("end", end);
+			}
+		}
+		
+		joinButton = LinkFactory.createButtonLarge("meeting.join.button", flc.getFormItemComponent(), this);
+		joinButton.setTarget("_blank");
+		joinButton.setVisible(!ended || moderator || administrator);
+	}
+	
+	private boolean isEnded() {
+		return meeting != null && meeting.getEndDate() != null && new Date().after(meeting.getEndDate());
+	}
+	
+	private boolean isValidDates() {
+		if(!userMeetingsDates) {
+			return true;
+		}
+		Date now = new Date();
+		Date start = meeting.getStartWithLeadTime();
+		Date end = meeting.getEndWithFollowupTime();
+		
+		return !((start != null && start.compareTo(now) >= 0) || (end != null && end.compareTo(now) <= 0));
+	}
+	
+	private void reloadButtonsAndStatus() {
+		meeting = bigBlueButtonManager.getMeeting(meeting);
+		updateButtonsAndStatus();
+	}
+	
+	private void updateButtonsAndStatus() {
+		boolean meetingsExists = StringHelper.containsNonWhitespace(meeting.getMeetingId());
+		boolean isEnded = isEnded();
+
+		flc.contextPut("meetingsExists", Boolean.valueOf(meetingsExists));
+		flc.contextPut("ended", Boolean.valueOf(isEnded));
+		
+		boolean accessible = !isEnded() || administrator || moderator;
+		boolean running = bigBlueButtonManager.isMeetingRunning(meeting);
+		if(moderator || administrator) {
+			joinButton.setVisible(accessible);
+			joinButton.setEnabled(!readOnly);
+			
+			if(!running && moderatorStartMeeting) {
+				joinButton.setCustomDisplayText(translate("meeting.start.button"));
+			} else if(isValidDates()) {
+				joinButton.setCustomDisplayText(translate("meeting.join.button"));
+			} else {
+				joinButton.setCustomDisplayText(translate("meeting.go.button"));
+			}
+		} else {
+			boolean validDates = isValidDates();
+
+			joinButton.setVisible(accessible);
+			if(!running && moderatorStartMeeting) {
+				joinButton.setEnabled(false);
+			} else {
+				joinButton.setEnabled(!readOnly && validDates);
+			}
+
+			if(validDates && !running && moderatorStartMeeting) {
+				flc.contextPut("notStarted", Boolean.TRUE);	
+			} else if(validDates || isEnded) {
+				flc.contextPut("notStarted", Boolean.FALSE);
+			} else {
+				flc.contextPut("notStarted", Boolean.TRUE);
+			}
+		}
+	}
+
+	@Override
+	protected void doDispose() {
+		CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, meetingOres);
+	}
+
+	@Override
+	public void event(Event event) {
+		if(event instanceof BigBlueButtonEvent) {
+			BigBlueButtonEvent ace = (BigBlueButtonEvent)event;
+			if(ace.getMeetingKey() != null && ace.getMeetingKey().equals(meeting.getKey())) {
+				reloadButtonsAndStatus();
+			}
+		}
+	}
+
+	@Override
+	public void event(UserRequest ureq, Component source, Event event) {
+		if(joinButton == source) {
+			doJoin(ureq);
+		}
+		super.event(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	private void doJoin(UserRequest ureq) {
+		meeting = bigBlueButtonManager.getMeeting(meeting);
+		if(meeting == null) {
+			showWarning("warning.no.meeting");
+			fireEvent(ureq, Event.BACK_EVENT);
+			return;
+		}
+		
+		String meetingUrl = null;
+		BigBlueButtonErrors errors = new BigBlueButtonErrors();
+		if(moderator || administrator) {
+			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), (administrator || moderator), false, errors);
+			BigBlueButtonEvent openEvent = new BigBlueButtonEvent(meeting.getKey(), getIdentity().getKey());
+			CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(openEvent, meetingOres);
+		} else if(!moderatorStartMeeting || bigBlueButtonManager.isMeetingRunning(meeting)) {
+			boolean guest = ureq.getUserSession().getRoles().isGuestOnly();
+			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), false, guest, errors);
+		}
+		redirectTo(ureq, meetingUrl, errors);
+	}
+	
+	private void redirectTo(UserRequest ureq, String meetingUrl, BigBlueButtonErrors errors) {
+		if(errors.hasErrors()) {
+			getWindowControl().setError(BigBlueButtonErrorHelper.formatErrors(getTranslator(), errors));
+		} else if(StringHelper.containsNonWhitespace(meetingUrl)) {
+			MediaResource redirect = new RedirectMediaResource(meetingUrl);
+			ureq.getDispatchResult().setResultingMediaResource(redirect);
+		} else {
+			showWarning("warning.no.access");
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingDefaultConfiguration.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingDefaultConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..983f13adf30e2dbbaf52aba257e268b722f93006
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingDefaultConfiguration.java
@@ -0,0 +1,39 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonMeetingDefaultConfiguration {
+	
+	private final boolean moderatorStart;
+	
+	public BigBlueButtonMeetingDefaultConfiguration(boolean moderatorStart) {
+		this.moderatorStart = moderatorStart;
+	}
+	
+	public boolean isModeratorStartMeeting() {
+		return moderatorStart;
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingTableModel.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..983104ad20ebf991a6cd534671be3cabb67e8f0d
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingTableModel.java
@@ -0,0 +1,121 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonMeetingTableModel extends DefaultFlexiTableDataModel<BigBlueButtonMeeting>
+implements SortableFlexiTableDataModel<BigBlueButtonMeeting> {
+	
+	private static final BMeetingsCols[] COLS = BMeetingsCols.values();
+	
+	private final Locale locale;
+	
+	public BigBlueButtonMeetingTableModel(FlexiTableColumnModel columnsModel, Locale locale) {
+		super(columnsModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey orderBy) {
+		if(orderBy != null) {
+			List<BigBlueButtonMeeting> views = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort();
+			super.setObjects(views);
+		}
+	}
+	
+	@Override
+	public Object getValueAt(int row, int col) {
+		BigBlueButtonMeeting meeting = getObject(row);
+		return getValueAt(meeting, col);
+	}
+
+	@Override
+	public Object getValueAt(BigBlueButtonMeeting row, int col) {
+		switch(COLS[col]) {
+			case name: return row.getName();
+			case permanent: return Boolean.valueOf(row.isPermanent());
+			case start: return row.getStartDate();
+			case end: return row.getEndDate();
+			case resource: return getResourceName(row);
+			default: return "ERROR";
+		}
+	}
+	
+	private String getResourceName(BigBlueButtonMeeting row) {
+		String displayName = null;
+		if(row.getEntry() != null) {
+			displayName = row.getEntry().getDisplayname();
+		} else if(row.getBusinessGroup() != null) {
+			displayName = row.getBusinessGroup().getName();
+		}
+		return displayName;
+	}
+
+	@Override
+	public BigBlueButtonMeetingTableModel createCopyWithEmptyList() {
+		return new BigBlueButtonMeetingTableModel(getTableColumnModel(), locale);
+	}
+	
+	public enum BMeetingsCols implements FlexiSortableColumnDef {
+		
+		name("meeting.name"),
+		permanent("table.header.permanent"),
+		start("meeting.start"),
+		end("meeting.end"),
+		resource("meeting.resource");
+		
+		private final String i18nHeaderKey;
+		
+		private BMeetingsCols(String i18nHeaderKey) {
+			this.i18nHeaderKey = i18nHeaderKey;
+		}
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+
+		@Override
+		public String i18nHeaderKey() {
+			return i18nHeaderKey;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingsController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..3dec4134a52eb21dfb16aead0da21ddb81fca8ae
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingsController.java
@@ -0,0 +1,195 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableSortOptions;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.ui.BigBlueButtonMeetingTableModel.BMeetingsCols;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonMeetingsController extends FormBasicController {
+	
+	private FlexiTableElement pastTableEl;
+	private FlexiTableElement upcomingTableEl;
+	private BigBlueButtonMeetingTableModel pastTableModel;
+	private BigBlueButtonMeetingTableModel upcomingTableModel;
+	
+	private final RepositoryEntry entry;
+	private final String subIdent;
+	private final BusinessGroup businessGroup;
+	private final boolean showPermanentCol;
+	
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public BigBlueButtonMeetingsController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry, String subIdent,
+			BusinessGroup businessGroup, boolean administrator, boolean moderator) {
+		super(ureq, wControl, "meetings");
+		this.entry = entry;
+		this.subIdent = subIdent;
+		this.businessGroup = businessGroup;
+		showPermanentCol = (administrator || moderator);
+		
+		initForm(ureq);
+		updateModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		// upcoming meetings table
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.name));
+		if(showPermanentCol) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.permanent));
+		}
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.start));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.end));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("select", translate("select"), "select"));
+
+		upcomingTableModel = new BigBlueButtonMeetingTableModel(columnsModel, getLocale());
+		upcomingTableEl = uifactory.addTableElement(getWindowControl(), "upcomingMeetings", upcomingTableModel, getTranslator(), formLayout);
+		upcomingTableEl.setEmtpyTableMessageKey("no.upcoming.meetings");
+		
+		FlexiTableSortOptions sortOptions = new FlexiTableSortOptions();
+		sortOptions.setDefaultOrderBy(new SortKey(BMeetingsCols.start.name(), false));
+		upcomingTableEl.setSortSettings(sortOptions);
+		upcomingTableEl.setAndLoadPersistedPreferences(ureq, "big-blue-button-upcoming-meetings-list");
+
+		// past meetings
+		FlexiTableColumnModel pastColumnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.name));
+		if(showPermanentCol) {
+			pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.permanent));
+		}
+		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.start));
+		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BMeetingsCols.end));
+		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("select", translate("select"), "select"));
+
+		pastTableModel = new BigBlueButtonMeetingTableModel(pastColumnsModel, getLocale());
+		pastTableEl = uifactory.addTableElement(getWindowControl(), "pastMeetings", pastTableModel, getTranslator(), formLayout);
+		
+		FlexiTableSortOptions pastSortOptions = new FlexiTableSortOptions();
+		pastSortOptions.setDefaultOrderBy(new SortKey(BMeetingsCols.start.name(), false));
+		pastTableEl.setSortSettings(pastSortOptions);
+		pastTableEl.setAndLoadPersistedPreferences(ureq, "big-blue-button-past-meetings-list");
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	public void updateModel() {
+		List<BigBlueButtonMeeting> meetings = bigBlueButtonManager.getMeetings(entry, subIdent, businessGroup);
+		
+		Date now = new Date();
+		List<BigBlueButtonMeeting> pastMeetings = new ArrayList<>();
+		List<BigBlueButtonMeeting> upcomingMeetings = new ArrayList<>();
+		for(BigBlueButtonMeeting meeting:meetings) {
+			if(meeting.getStartDate() == null || meeting.getEndDate() == null
+					|| now.compareTo(meeting.getEndDate()) <= 0) {
+				upcomingMeetings.add(meeting);
+			} else {
+				pastMeetings.add(meeting);
+			}
+		}
+		
+		upcomingTableModel.setObjects(upcomingMeetings);
+		upcomingTableEl.reset(true, true, true);
+		pastTableModel.setObjects(pastMeetings);
+		pastTableEl.reset(true, true, true);
+		pastTableEl.setVisible(!pastMeetings.isEmpty());
+	}
+	
+	public boolean hasMeetingByKey(Long meetingKey) {
+		boolean has = upcomingTableModel.getObjects().stream()
+			.anyMatch(m -> meetingKey.equals(m.getKey()));
+		return has || pastTableModel.getObjects().stream()
+				.anyMatch(m -> meetingKey.equals(m.getKey()));
+	}
+	
+	public BigBlueButtonMeeting getMeetingByKey(Long meetingKey) {
+		Optional<BigBlueButtonMeeting> has = upcomingTableModel.getObjects().stream()
+				.filter(m -> meetingKey.equals(m.getKey())).findFirst();
+		if(!has.isPresent()) {
+			has = pastTableModel.getObjects().stream()
+					.filter(m -> meetingKey.equals(m.getKey())).findFirst();
+		}
+		return has.isPresent() ? has.get() : null;
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(upcomingTableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("select".equals(se.getCommand())) {
+					doSelect(ureq, upcomingTableModel.getObject(se.getIndex()));
+				}
+			}
+		} else if(pastTableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("select".equals(se.getCommand())) {
+					doSelect(ureq, pastTableModel.getObject(se.getIndex()));
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	private void doSelect(UserRequest ureq, BigBlueButtonMeeting meeting) {
+		fireEvent(ureq, new SelectBigBlueButtonMeetingEvent(meeting));
+	}
+	
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonRunController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonRunController.java
new file mode 100644
index 0000000000000000000000000000000000000000..84cb617a7983c69f91afc68b7f0c110048316868
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonRunController.java
@@ -0,0 +1,223 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.components.segmentedview.SegmentViewComponent;
+import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
+import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.core.gui.control.generic.dtabs.Activateable2;
+import org.olat.core.id.context.ContextEntry;
+import org.olat.core.id.context.StateEntry;
+import org.olat.core.util.resource.OresHelper;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonRunController extends BasicController implements Activateable2 {
+	
+	private Link adminLink;
+	private Link meetingsLink;
+	private final Link backLink;
+	
+	private final VelocityContainer mainVC;
+	private SegmentViewComponent segmentView;
+	
+	private BigBlueButtonMeetingController meetingCtrl;
+	private BigBlueButtonMeetingsController meetingsCtrl;
+	private BigBlueButtonEditMeetingsController adminCtrl;
+	
+	private final String subIdent;
+	private final BusinessGroup group;
+	private final RepositoryEntry entry;
+	private final BigBlueButtonMeetingDefaultConfiguration configuration;
+	
+	private boolean canView;
+	private boolean readOnly;
+	private boolean moderator;
+	private boolean administrator;
+	
+	public BigBlueButtonRunController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry, String subIdentifier,
+			BusinessGroup group, BigBlueButtonMeetingDefaultConfiguration configuration, boolean admin, boolean moderator, boolean readOnly) {
+		super(ureq, wControl);
+		this.subIdent = subIdentifier;
+		this.group = group;
+		this.entry = entry;
+		this.readOnly = readOnly;
+		this.configuration = configuration;
+		this.administrator = admin;
+		this.moderator = moderator;
+		
+		canView = !ureq.getUserSession().getRoles().isGuestOnly();
+
+		if(!canView) {
+			//no accessible to guests
+			mainVC = createVelocityContainer("run");
+		} else if(administrator) {
+			mainVC = createVelocityContainer("run_admin");
+			
+			segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+			meetingsLink = LinkFactory.createLink("meetings.title", mainVC, this);
+			segmentView.addSegment(meetingsLink, true);
+			
+			adminLink = LinkFactory.createLink("meetings.admin.title", mainVC, this);
+			segmentView.addSegment(adminLink, false);
+			
+			doOpenMeetings(ureq);
+		} else {
+			mainVC = createVelocityContainer("run");
+			meetingsCtrl = new BigBlueButtonMeetingsController(ureq, wControl, entry, subIdent, group, admin, moderator);
+			listenTo(meetingsCtrl);
+			mainVC.put("meetings", meetingsCtrl.getInitialComponent());
+		}
+		backLink = LinkFactory.createLinkBack(mainVC, this);
+
+		putInitialPanel(mainVC);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	@Override
+	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
+		if(entries == null || entries.isEmpty()) return;
+		
+		String type = entries.get(0).getOLATResourceable().getResourceableTypeName();
+		if("Meetings".equalsIgnoreCase(type)) {
+			if(canView) {
+				doOpenMeetings(ureq);
+				if(segmentView != null) {
+					segmentView.select(meetingsLink);
+				}
+			}
+		} else if("Administration".equalsIgnoreCase(type)) {
+			if(administrator) {
+				doOpenAdmin(ureq);
+				if(segmentView != null) { 
+					segmentView.select(adminLink);
+				}
+			}
+		} else if("Meeting".equalsIgnoreCase(type)) {
+			if(canView) {
+				doOpenMeetings(ureq);
+				if(segmentView != null) {
+					segmentView.select(meetingsLink);
+				}
+				Long meetingKey = entries.get(0).getOLATResourceable().getResourceableId();
+				if(meetingsCtrl.hasMeetingByKey(meetingKey)) {
+					doSelectMeeting(ureq, meetingsCtrl.getMeetingByKey(meetingKey));
+				}
+			}
+		}
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(backLink == source) {
+			back();
+		} else if(source == segmentView) {
+			if(event instanceof SegmentViewEvent) {
+				SegmentViewEvent sve = (SegmentViewEvent)event;
+				String segmentCName = sve.getComponentName();
+				Component clickedLink = mainVC.getComponent(segmentCName);
+				if (clickedLink == meetingsLink) {
+					doOpenMeetings(ureq);
+				} else if (clickedLink == adminLink){
+					doOpenAdmin(ureq);
+				}
+			}
+		}
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(meetingsCtrl == source) {
+			if(event instanceof SelectBigBlueButtonMeetingEvent) {
+				SelectBigBlueButtonMeetingEvent se = (SelectBigBlueButtonMeetingEvent)event;
+				doSelectMeeting(ureq, se.getMeeting());
+			}
+		} else if(meetingCtrl == source) {
+			if(event == Event.BACK_EVENT) {
+				back();
+			}
+		}
+	}
+	
+	private void back() {
+		mainVC.remove(meetingCtrl.getInitialComponent());
+		removeAsListenerAndDispose(meetingCtrl);
+		meetingCtrl = null;
+	}
+	
+	private void doOpenMeetings(UserRequest ureq) {
+		if(meetingsCtrl == null) {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Meetings", 0l), null);
+			meetingsCtrl = new BigBlueButtonMeetingsController(ureq, bwControl,
+					entry, subIdent, group, administrator, moderator);
+			listenTo(meetingsCtrl);
+		} else {
+			meetingsCtrl.updateModel();
+			addToHistory(ureq, meetingsCtrl);
+		}
+		mainVC.put("segmentCmp", meetingsCtrl.getInitialComponent());
+	}
+	
+	private void doOpenAdmin(UserRequest ureq) {
+		if(adminCtrl == null) {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Administration", 0l), null);
+			adminCtrl = new BigBlueButtonEditMeetingsController(ureq, bwControl, entry, subIdent, group, readOnly);
+			listenTo(adminCtrl);
+		} else {
+			addToHistory(ureq, adminCtrl);
+		}
+		mainVC.put("segmentCmp", adminCtrl.getInitialComponent());
+	}
+	
+	private void doSelectMeeting(UserRequest ureq, BigBlueButtonMeeting meeting) {
+		removeAsListenerAndDispose(meetingCtrl);
+		meetingCtrl = null;
+		
+		if(meeting == null) {
+			showWarning("warning.no.meeting");
+		} else {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Meeting", meeting.getKey()), null);
+			meetingCtrl = new BigBlueButtonMeetingController(ureq, bwControl, meeting, configuration, administrator, moderator, readOnly);
+			listenTo(meetingCtrl);
+			mainVC.put("meeting", meetingCtrl.getInitialComponent());
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd1cdc551d7d54f2dc8847e628affa087574dfa9
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java
@@ -0,0 +1,105 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+
+/**
+ * 
+ * Initial date: 19 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonTemplateTableModel extends DefaultFlexiTableDataModel<BigBlueButtonMeetingTemplate>
+implements SortableFlexiTableDataModel<BigBlueButtonMeetingTemplate> {
+	
+	private static final BTemplatesCols[] COLS = BTemplatesCols.values();
+	
+	private final Locale locale;
+	
+	public BigBlueButtonTemplateTableModel(FlexiTableColumnModel columnsModel, Locale locale) {
+		super(columnsModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey orderBy) {
+		if(orderBy != null) {
+			List<BigBlueButtonMeetingTemplate> views = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort();
+			super.setObjects(views);
+		}
+	}
+	
+	@Override
+	public Object getValueAt(int row, int col) {
+		BigBlueButtonMeetingTemplate meeting = getObject(row);
+		return getValueAt(meeting, col);
+	}
+
+	@Override
+	public Object getValueAt(BigBlueButtonMeetingTemplate row, int col) {
+		switch(COLS[col]) {
+			case name: return row.getName();
+			case system: return Boolean.valueOf(row.isSystem());
+			default: return "ERROR";
+		}
+	}
+
+	@Override
+	public BigBlueButtonTemplateTableModel createCopyWithEmptyList() {
+		return new BigBlueButtonTemplateTableModel(getTableColumnModel(), locale);
+	}
+	
+	public enum BTemplatesCols implements FlexiSortableColumnDef {
+		
+		name("meeting.name"),
+		system("table.header.system");
+		
+		private final String i18nHeaderKey;
+		
+		private BTemplatesCols(String i18nHeaderKey) {
+			this.i18nHeaderKey = i18nHeaderKey;
+		}
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+
+		@Override
+		public String i18nHeaderKey() {
+			return i18nHeaderKey;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
new file mode 100644
index 0000000000000000000000000000000000000000..3396fa973eca838dc1de642d6b4108a7493e2278
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
@@ -0,0 +1,300 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.DateChooser;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.util.KeyValues;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EditBigBlueButtonMeetingController extends FormBasicController {
+	
+	private static final String[] permKeys = new String[] { "on" };
+	
+	private TextElement nameEl;
+	private TextElement descriptionEl;
+	private TextElement welcomeEl;
+	private TextElement leadTimeEl;
+	private TextElement followupTimeEl;
+	private DateChooser startDateEl;
+	private DateChooser endDateEl;
+	private SingleSelection templateEl;
+	private MultipleSelectionElement permanentEl;
+	
+	private final String subIdent;
+	private final RepositoryEntry entry;
+	private final BusinessGroup businessGroup;
+	private BigBlueButtonMeeting meeting;
+	private List<BigBlueButtonMeetingTemplate> templates;
+	
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public EditBigBlueButtonMeetingController(UserRequest ureq, WindowControl wControl,
+			RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
+		super(ureq, wControl);
+		this.entry = entry;
+		this.subIdent = subIdent;
+		this.businessGroup = businessGroup;
+		templates = bigBlueButtonManager.getTemplates();
+		
+		initForm(ureq);
+		updateUI();
+	}
+	
+	public EditBigBlueButtonMeetingController(UserRequest ureq, WindowControl wControl, BigBlueButtonMeeting meeting) {
+		super(ureq, wControl);
+		entry = meeting.getEntry();
+		subIdent = meeting.getSubIdent();
+		businessGroup = meeting.getBusinessGroup();
+		this.meeting = meeting;
+		templates = bigBlueButtonManager.getTemplates();
+		
+		initForm(ureq);
+		updateUI();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String name = meeting == null ? "" : meeting.getName();
+		nameEl = uifactory.addTextElement("meeting.name", "meeting.name", 128, name, formLayout);
+		nameEl.setMandatory(true);
+		if(!StringHelper.containsNonWhitespace(name)) {
+			nameEl.setFocus(true);
+		}
+		
+		String description = meeting == null ? "" : meeting.getDescription();
+		descriptionEl = uifactory.addTextAreaElement("meeting.description", "meeting.description", 2000, 4, 72, false, false, description, formLayout);
+
+		String welcome = meeting == null ? "" : meeting.getWelcome();
+		welcomeEl = uifactory.addRichTextElementForStringDataMinimalistic("meeting.welcome", "meeting.welcome", welcome, 8, 60, formLayout, getWindowControl());
+		
+		KeyValues templatesKeyValues = new KeyValues();
+		templatesKeyValues.add(KeyValues.entry("-", translate("meeting.no.template")));
+		for(BigBlueButtonMeetingTemplate template:templates) {
+			templatesKeyValues.add(KeyValues.entry(template.getKey().toString(), template.getName()));
+		}
+		String[] templatesKeys = templatesKeyValues.keys();
+		templateEl = uifactory.addDropdownSingleselect("meeting.template", "meeting.template", formLayout,
+				templatesKeys, templatesKeyValues.values());
+		templateEl.setMandatory(true);
+		boolean templateSelected = false;
+		if(meeting != null && meeting.getTemplate() != null) {
+			String currentTemplateId = meeting.getTemplate().getKey().toString();
+			for(String key:templatesKeys) {
+				if(currentTemplateId.equals(key)) {
+					templateEl.select(currentTemplateId, true);
+					templateSelected = true;
+				}
+			}
+		}
+		if(!templateSelected) {
+			templateEl.select(templatesKeys[0], true);
+		}
+		
+		String[] permValues = new String[] { translate("meeting.permanent.on") };
+		permanentEl = uifactory.addCheckboxesHorizontal("meeting.permanent", formLayout, permKeys, permValues);
+		permanentEl.addActionListener(FormEvent.ONCHANGE);
+		boolean permanent = meeting == null ? false : meeting.isPermanent();
+		permanentEl.select(permKeys[0], permanent);
+		permanentEl.setHelpTextKey("meeting.permanent.explain", null);
+
+		Date startDate = meeting == null ? null : meeting.getStartDate();
+		startDateEl = uifactory.addDateChooser("meeting.start", "meeting.start", startDate, formLayout);
+		startDateEl.setMandatory(!permanent);
+		startDateEl.setDateChooserTimeEnabled(true);
+		
+		String leadtime = meeting == null ? null : Long.toString(meeting.getLeadTime());
+		leadTimeEl = uifactory.addTextElement("meeting.leadTime", 8, leadtime, formLayout);
+		
+		Date endDate = meeting == null ? null : meeting.getEndDate();
+		endDateEl = uifactory.addDateChooser("meeting.end", "meeting.end", endDate, formLayout);
+		endDateEl.setMandatory(!permanent);
+		endDateEl.setDefaultValue(startDateEl);
+		endDateEl.setDateChooserTimeEnabled(true);
+		
+		String followup = meeting == null ? null : Long.toString(meeting.getFollowupTime());
+		followupTimeEl = uifactory.addTextElement("meeting.followupTime", 8, followup, formLayout);
+		
+		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add("buttons", buttonLayout);
+		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("save", buttonLayout);
+	}
+	
+	private void updateUI() {
+		boolean permanent = permanentEl.isAtLeastSelected(1);
+		startDateEl.setVisible(!permanent);
+		leadTimeEl.setVisible(!permanent);
+		endDateEl.setVisible(!permanent);
+		followupTimeEl.setVisible(!permanent);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		nameEl.clearError();
+		if(!StringHelper.containsNonWhitespace(nameEl.getValue())) {
+			nameEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		} else if (nameEl.getValue().contains("&")) {
+			nameEl.setErrorKey("form.invalidchar.noamp", null);
+			allOk &= false;
+		}
+		
+		startDateEl.clearError();
+		endDateEl.clearError();
+		if(!permanentEl.isAtLeastSelected(1)) {
+			if(startDateEl.getDate() == null) {
+				startDateEl.setErrorKey("form.legende.mandatory", null);
+				allOk &= false;
+			}
+			if(endDateEl.getDate() == null) {
+				endDateEl.setErrorKey("form.legende.mandatory", null);
+				allOk &= false;
+			}
+			
+			if(startDateEl.getDate() != null && endDateEl.getDate() != null) {
+				Date start = startDateEl.getDate();
+				Date end = endDateEl.getDate();
+				if(end.before(start)) {
+					endDateEl.setErrorKey("error.start.after.end", null);
+					allOk &= false;
+				}
+			}
+		}
+		
+		allOk &= validateTime(leadTimeEl);
+		allOk &= validateTime(followupTimeEl);
+		return allOk;
+	}
+	
+	private boolean validateTime(TextElement el) {
+		boolean allOk = true;
+		el.clearError();
+		if(StringHelper.containsNonWhitespace(el.getValue()) && !StringHelper.isLong(el.getValue())) {
+			el.setErrorKey("form.error.nointeger", null);
+			allOk &= false;
+		}
+		return allOk;
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(permanentEl == source) {
+			updateUI();
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		if(meeting == null) {
+			meeting = bigBlueButtonManager.createAndPersistMeeting(nameEl.getValue(), entry, subIdent, businessGroup);
+		} else {
+			meeting = bigBlueButtonManager.getMeeting(meeting);
+			meeting.setName(nameEl.getValue());
+		}
+		
+		meeting.setDescription(descriptionEl.getValue());
+		meeting.setWelcome(welcomeEl.getValue());
+		BigBlueButtonMeetingTemplate template = null;
+		if(templateEl.isOneSelected() && !"-".equals(templateEl.getSelectedKey())) {
+			String selectedTemplateId = templateEl.getSelectedKey();
+			template = templates.stream()
+					.filter(tpl -> selectedTemplateId.equals(tpl.getKey().toString()))
+					.findFirst()
+					.orElse(null);
+		}
+		meeting.setTemplate(template);
+		
+		boolean permanent;	
+		if(permanentEl.isVisible()) {
+			permanent = permanentEl.isAtLeastSelected(1);
+		} else {
+			permanent = false;
+		}
+		meeting.setPermanent(permanent);
+		if(permanent) {
+			meeting.setStartDate(null);
+			meeting.setEndDate(null);
+			meeting.setLeadTime(0l);
+			meeting.setFollowupTime(0l);
+		} else {
+			Date startDate = startDateEl.getDate();
+			Date endDate = endDateEl.getDate();
+			meeting.setStartDate(startDate);
+			meeting.setEndDate(endDate);
+			
+			long leadTime = 0;
+			if(leadTimeEl.isVisible() && StringHelper.isLong(leadTimeEl.getValue())) {
+				leadTime = Long.valueOf(leadTimeEl.getValue());
+			}
+			meeting.setLeadTime(leadTime);
+			
+			long followupTime = 0;
+			if(followupTimeEl.isVisible() && StringHelper.isLong(followupTimeEl.getValue())) {
+				followupTime = Long.valueOf(followupTimeEl.getValue());
+			}
+			meeting.setFollowupTime(followupTime);
+		}
+		
+		bigBlueButtonManager.updateMeeting(meeting);
+
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java
new file mode 100644
index 0000000000000000000000000000000000000000..224e4c90afc3db0f6823dd5f4ffca6ed2969e837
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java
@@ -0,0 +1,282 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.util.KeyValues;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonManager;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.modules.bigbluebutton.GuestPolicyEnum;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 19 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EditBigBlueButtonTemplateController extends FormBasicController {
+
+	private static final String[] maxParticipantsKeys = new String[] { "2", "5", "10", "25", "50", "100" };
+	private static final String[] onKeys = new String[] { "yes", "no", "default" };
+	private static final String[] guestPolicyKeys = new String[] {
+			GuestPolicyEnum.ALWAYS_DENY.name(), GuestPolicyEnum.ASK_MODERATOR.name(), GuestPolicyEnum.ALWAYS_ACCEPT.name()
+		};
+	
+	private TextElement nameEl;
+	private TextElement descriptionEl;
+	private SingleSelection muteOnStartEl;
+	private SingleSelection maxParticipantsEl;
+	private SingleSelection autoStartRecordingEl;
+	private SingleSelection allowStartStopRecordingEl;
+	private SingleSelection webcamsOnlyForModeratorEl;
+	private SingleSelection allowModsToUnmuteUsersEl;
+	private SingleSelection lockSettingsDisableCamEl;
+	private SingleSelection lockSettingsDisableMicEl;
+	private SingleSelection lockSettingsDisablePrivateChatEl;
+	private SingleSelection lockSettingsDisablePublicChatEl;
+	private SingleSelection lockSettingsDisableNoteEl;
+	private SingleSelection lockSettingsLockedLayoutEl;
+
+	private SingleSelection guestPolicyEl;
+	
+	private BigBlueButtonMeetingTemplate template;
+	
+	@Autowired
+	private BigBlueButtonManager bigBlueButtonManager;
+	
+	public EditBigBlueButtonTemplateController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		initForm(ureq);
+	}
+	
+	public EditBigBlueButtonTemplateController(UserRequest ureq, WindowControl wControl, BigBlueButtonMeetingTemplate template) {
+		super(ureq, wControl);
+		this.template = template;
+
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String name = template == null ? "" : template.getName();
+		nameEl = uifactory.addTextElement("template.name", "template.name", 128, name, formLayout);
+		nameEl.setMandatory(true);
+		if(!StringHelper.containsNonWhitespace(name)) {
+			nameEl.setFocus(true);
+		}
+		
+		String description = template == null ? "" : template.getDescription();
+		descriptionEl = uifactory.addTextAreaElement("template.description", "template.description", 2000, 4, 72, false, false, description, formLayout);
+		
+		String maxParticipants = template == null || template.getMaxParticipants() == null ? "-" : template.getMaxParticipants().toString();
+		KeyValues maxParticipantsKeyValues = new KeyValues();
+		maxParticipantsKeyValues.add(KeyValues.entry("-", translate("template.maxParticipants.default")));
+		for(String maxParticipantsKey:maxParticipantsKeys) {
+			maxParticipantsKeyValues.add(KeyValues.entry(maxParticipantsKey, maxParticipantsKey));
+		}
+		if(!maxParticipantsKeyValues.containsKey(maxParticipants)) {
+			maxParticipantsKeyValues.add(KeyValues.entry(maxParticipants, maxParticipants));
+		}
+		maxParticipantsEl = uifactory.addDropdownSingleselect("template.maxParticipants", formLayout,
+				maxParticipantsKeyValues.keys(), maxParticipantsKeyValues.values());
+		maxParticipantsEl.select(maxParticipants, true);
+		
+		String[] onValues = new String[] { "Yes", "No", "Default" };
+		
+		Boolean muteOnStart = template == null ? null : template.getMuteOnStart();
+		muteOnStartEl = uifactory.addRadiosHorizontal("template.muteOnStart", formLayout, onKeys, onValues);
+		select(muteOnStart, muteOnStartEl);
+		
+		Boolean autoStartRecording = template == null ? null : template.getAutoStartRecording();
+		autoStartRecordingEl = uifactory.addRadiosHorizontal("template.autoStartRecording", formLayout, onKeys, onValues);
+		select(autoStartRecording, autoStartRecordingEl);
+		
+		Boolean allowStartStopRecording = template == null ? null : template.getAllowStartStopRecording();
+		allowStartStopRecordingEl = uifactory.addRadiosHorizontal("template.allowStartStopRecording", formLayout, onKeys, onValues);
+		select(allowStartStopRecording, allowStartStopRecordingEl);
+		
+		Boolean webcamsOnlyForModerator = template == null ? null : template.getWebcamsOnlyForModerator();
+		webcamsOnlyForModeratorEl = uifactory.addRadiosHorizontal("template.webcamsOnlyForModerator", formLayout, onKeys, onValues);
+		select(webcamsOnlyForModerator, webcamsOnlyForModeratorEl);
+		
+		Boolean allowModsToUnmuteUsers = template == null ? null : template.getAllowModsToUnmuteUsers();
+		allowModsToUnmuteUsersEl = uifactory.addRadiosHorizontal("template.allowModsToUnmuteUsers", formLayout, onKeys, onValues);
+		select(allowModsToUnmuteUsers, allowModsToUnmuteUsersEl);
+		
+		Boolean lockSettingsDisableCam = template == null ? null : template.getLockSettingsDisableCam();
+		lockSettingsDisableCamEl = uifactory.addRadiosHorizontal("template.lockSettingsDisableCam", formLayout, onKeys, onValues);
+		select(lockSettingsDisableCam, lockSettingsDisableCamEl);
+		
+		Boolean lockSettingsDisableMic = template == null ? null : template.getLockSettingsDisableMic();
+		lockSettingsDisableMicEl = uifactory.addRadiosHorizontal("template.lockSettingsDisableMic", formLayout, onKeys, onValues);
+		select(lockSettingsDisableMic, lockSettingsDisableMicEl);
+		
+		Boolean lockSettingsDisablePrivateChat = template == null ? null : template.getLockSettingsDisablePrivateChat();
+		lockSettingsDisablePrivateChatEl =  uifactory.addRadiosHorizontal("template.lockSettingsDisablePrivateChat", formLayout, onKeys, onValues);
+		select(lockSettingsDisablePrivateChat, lockSettingsDisablePrivateChatEl);
+		
+		Boolean lockSettingsDisablePublicChat = template == null ? null : template.getLockSettingsDisablePublicChat();
+		lockSettingsDisablePublicChatEl =  uifactory.addRadiosHorizontal("template.lockSettingsDisablePublicChat", formLayout, onKeys, onValues);
+		select(lockSettingsDisablePublicChat, lockSettingsDisablePublicChatEl);
+		
+		Boolean lockSettingsDisableNote = template == null ? null : template.getLockSettingsDisableNote();
+		lockSettingsDisableNoteEl =  uifactory.addRadiosHorizontal("template.lockSettingsDisableNote", formLayout, onKeys, onValues);
+		select(lockSettingsDisableNote, lockSettingsDisableNoteEl);
+		
+		Boolean lockSettingsLockedLayout = template == null ? null : template.getLockSettingsLockedLayout();
+		lockSettingsLockedLayoutEl = uifactory.addRadiosHorizontal("template.lockSettingsLockedLayout", formLayout, onKeys, onValues);
+		select(lockSettingsLockedLayout, lockSettingsLockedLayoutEl);
+
+		GuestPolicyEnum guestPolicy = template == null ? GuestPolicyEnum.ALWAYS_DENY : template.getGuestPolicyEnum();
+		String[] guestPolicyValues = new String[] {
+				translate("guest.policy.always.deny"), translate("guest.policy.ask.moderator"),
+				translate("guest.policy.always.accept")
+			};
+		guestPolicyEl = uifactory.addRadiosHorizontal("template.guestPolicy", formLayout, guestPolicyKeys, guestPolicyValues);
+		guestPolicyEl.select(guestPolicy.name(), true);
+		
+		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add("buttons", buttonLayout);
+		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+		
+		if(template == null ||  !template.isSystem()) {
+			uifactory.addFormSubmitButton("save", buttonLayout);
+		} else {
+			disableSystemTemplate();
+		}
+	}
+	
+	private void disableSystemTemplate() {
+		nameEl.setEnabled(false);
+		descriptionEl.setEnabled(false);
+		muteOnStartEl.setEnabled(false);
+		maxParticipantsEl.setEnabled(false);
+		autoStartRecordingEl.setEnabled(false);
+		allowStartStopRecordingEl.setEnabled(false);
+		webcamsOnlyForModeratorEl.setEnabled(false);
+		allowModsToUnmuteUsersEl.setEnabled(false);
+		lockSettingsDisableCamEl.setEnabled(false);
+		lockSettingsDisableMicEl.setEnabled(false);
+		lockSettingsDisablePrivateChatEl.setEnabled(false);
+		lockSettingsDisablePublicChatEl.setEnabled(false);
+		lockSettingsDisableNoteEl.setEnabled(false);
+		lockSettingsLockedLayoutEl.setEnabled(false);
+		guestPolicyEl.setEnabled(false);
+	}
+	
+	private void select(Boolean val, SingleSelection selectEl) {
+		if(val == null) {
+			selectEl.select(onKeys[2], true);
+		} else if(val.booleanValue()) {
+			selectEl.select(onKeys[0], true);
+		} else {
+			selectEl.select(onKeys[1], true);
+		}
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		nameEl.clearError();
+		if(!StringHelper.containsNonWhitespace(nameEl.getValue())) {
+			nameEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		} else if(nameEl.getValue().length() > 128) {
+			nameEl.setErrorKey("form.error.toolong", new String[] { "128" });
+			allOk &= false;
+		}
+		
+		descriptionEl.clearError();
+		if(!StringHelper.containsNonWhitespace(descriptionEl.getValue()) && descriptionEl.getValue().length() > 2000) {
+			nameEl.setErrorKey("form.error.toolong", new String[] { "2000" });
+			allOk &= false;
+		}
+		
+		return allOk;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		if(template == null) {
+			template = bigBlueButtonManager.createAndPersistTemplate(nameEl.getValue());
+		} else {
+			template.setName(nameEl.getValue());
+		}
+		template.setDescription(descriptionEl.getValue());
+		
+		if(maxParticipantsEl.isOneSelected() && !"-".equals(maxParticipantsEl.getSelectedKey())) {
+			template.setMaxParticipants(Integer.parseInt(maxParticipantsEl.getSelectedKey()));
+		} else {
+			template.setMaxParticipants(null);
+		}
+		
+		template.setMuteOnStart(getSelected(muteOnStartEl));
+		template.setAutoStartRecording(getSelected(autoStartRecordingEl));
+		template.setAllowStartStopRecording(getSelected(allowStartStopRecordingEl));
+		template.setWebcamsOnlyForModerator(getSelected(webcamsOnlyForModeratorEl));
+		template.setAllowModsToUnmuteUsers(getSelected(allowModsToUnmuteUsersEl));
+		template.setLockSettingsDisableCam(getSelected(lockSettingsDisableCamEl));
+		template.setLockSettingsDisableMic(getSelected(lockSettingsDisableMicEl));
+		template.setLockSettingsDisablePrivateChat(getSelected(lockSettingsDisablePrivateChatEl));
+		template.setLockSettingsDisablePublicChat(getSelected(lockSettingsDisablePublicChatEl));
+		template.setLockSettingsDisableNote(getSelected(lockSettingsDisableNoteEl));
+		template.setLockSettingsLockedLayout(getSelected(lockSettingsLockedLayoutEl));
+		
+		if(guestPolicyEl.isOneSelected()) {
+			template.setGuestPolicyEnum(GuestPolicyEnum.valueOf(guestPolicyEl.getSelectedKey()));
+		}
+		template = bigBlueButtonManager.updateTemplate(template);
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+	
+	private Boolean getSelected(SingleSelection selectEl) {
+		Boolean val = null;
+		if(selectEl.isOneSelected()) {
+			String selected = selectEl.getSelectedKey();
+			if("yes".equals(selected)) {
+				val = Boolean.TRUE;
+			} else if("no".equals(selected)) {
+				val = Boolean.FALSE;
+			}
+		}
+		return val;
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/SelectBigBlueButtonMeetingEvent.java b/src/main/java/org/olat/modules/bigbluebutton/ui/SelectBigBlueButtonMeetingEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..57f1593ca17c421584940dbfa6c622ab53448b97
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/SelectBigBlueButtonMeetingEvent.java
@@ -0,0 +1,48 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.ui;
+
+import org.olat.core.gui.control.Event;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SelectBigBlueButtonMeetingEvent extends Event {
+
+	private static final long serialVersionUID = 8960848450970463915L;
+
+	public static final String SELECT = "select-bigbluebutton-meeting";
+	
+	private final BigBlueButtonMeeting meeting;
+	
+	public SelectBigBlueButtonMeetingEvent(BigBlueButtonMeeting meeting) {
+		super(SELECT);
+		this.meeting = meeting;
+	}
+
+	public BigBlueButtonMeeting getMeeting() {
+		return meeting;
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/adminconfig.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/adminconfig.html
new file mode 100644
index 0000000000000000000000000000000000000000..6a92bcaf8eac507fffa21003584bbeabee4b6b30
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/adminconfig.html
@@ -0,0 +1,6 @@
+<fieldset>
+	<legend>$r.contextHelpWithWrapper("Communication and Collaboration#_bigbluebutton_config")
+	$r.translate("bigbluebutton.title")</legend>
+	<p class="o_info">$r.translate("bigbluebutton.intro")</p>
+	$r.render("flc_module")
+</fieldset>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/bbb_admin.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/bbb_admin.html
new file mode 100644
index 0000000000000000000000000000000000000000..8354fa8221272c56613d8899aeb273e9a203f53b
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/bbb_admin.html
@@ -0,0 +1,7 @@
+<div class="clearfix">
+	$r.render("segments")<br>	
+		
+	#if($r.available("segmentCmp"))
+		$r.render("segmentCmp")
+	#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html
new file mode 100644
index 0000000000000000000000000000000000000000..897c719b205351815dc72e35df00bd8bb8aa91a9
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html
@@ -0,0 +1,28 @@
+<h3>$r.escapeHtml($title)</h3>
+#if($errorMessage && !${errorMessage.isEmpty()})
+	<div class="o_error">$errorMessage</div>
+#end
+#if($r.isNotNull($start) || $r.isNotNull($end))
+<div><i class="o_icon o_icon_lifecycle_date"> </i> #if($r.isNotNull($start))$start#end - #if($r.isNotNull($end))$end#end</div>
+#end
+#if($description && !${description.isEmpty()})
+	<div class="o_block_large o_info">$r.xssScan($description)</div>
+#end
+#if($r.isTrue($ended))
+	<div class="o_block_large o_warning">$r.translate("meeting.ended")</div>
+#end
+
+#if($r.isFalse($meetingsExists) || $r.isTrue($notStarted))
+<div class="o_block_large o_info">$r.translate("meeting.create.intro")</div>
+#end
+
+<div class="o_button_group">
+#if($r.available("meeting.start.button") && $r.visible("meeting.start.button"))
+	$r.render("meeting.start.button")
+#end
+#if($r.available("meeting.join.button") && $r.visible("meeting.join.button"))
+	$r.render("meeting.join.button")
+#end
+</div>
+
+
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meetings.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meetings.html
new file mode 100644
index 0000000000000000000000000000000000000000..ea913fe6bd88b3fce4ae6b62975a523c646eeb8c
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meetings.html
@@ -0,0 +1,6 @@
+<h3>$r.translate("meetings.upcoming")</h3>
+$r.render("upcomingMeetings")
+#if($r.visible("pastMeetings"))
+	<h3>$r.translate("meetings.past")</h3>
+	$r.render("pastMeetings")
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meetings_admin.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meetings_admin.html
new file mode 100644
index 0000000000000000000000000000000000000000..d38da72edd2188916046416a28d5ff2a78aed860
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meetings_admin.html
@@ -0,0 +1,6 @@
+#if($r.available("add.meeting"))
+<div class="o_button_group o_button_group_right">
+	$r.render("add.meeting")
+</div>
+#end
+$r.render("meetings")
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/run.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/run.html
new file mode 100644
index 0000000000000000000000000000000000000000..25de423a882415fae77dc640300ab4f0cf8ebac2
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/run.html
@@ -0,0 +1,8 @@
+<div class="clearfix">
+#if($r.available("meeting"))
+	$r.render("backLink")
+	$r.render("meeting")
+#else
+	$r.render("meetings")
+#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/run_admin.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/run_admin.html
new file mode 100644
index 0000000000000000000000000000000000000000..49cfbf886d4e6062b664b94ce2da7b81a458f72a
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/run_admin.html
@@ -0,0 +1,11 @@
+<div class="clearfix">
+#if($r.available("meeting"))
+	$r.render("backLink")
+	$r.render("meeting")
+#else
+	$r.render("segments")<br>	
+	#if($r.available("segmentCmp"))
+		$r.render("segmentCmp")
+	#end
+#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/templates_admin.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/templates_admin.html
new file mode 100644
index 0000000000000000000000000000000000000000..cfac019f2b6cc116e3deb795a158df7025b63093
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/templates_admin.html
@@ -0,0 +1,6 @@
+#if($r.available("add.template"))
+<div class="o_button_group o_button_group_right">
+	$r.render("add.template")
+</div>
+#end
+$r.render("templates")
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
new file mode 100644
index 0000000000000000000000000000000000000000..34586b0fdb14583e89606b021eec58ec7a5cb2c7
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
@@ -0,0 +1,77 @@
+#Thu Mar 19 21:29:32 CET 2020
+account.configuration=Konfiguration
+add.meeting=Meeting hinzuf\u00FCgen
+add.template=Vorlage erstellen
+admin.menu.title=Big Blue Button
+admin.menu.title.alt=Big Blue Button
+bigbluebutton.intro=Intro
+bigbluebutton.module.enabled=Modul "Big Blue Button"
+bigbluebutton.module.enabled.for=Einschalten f\u00FCr
+bigbluebutton.module.enabled.for.courses=Kurse
+bigbluebutton.module.enabled.for.groups=Gruppen
+bigbluebutton.title=Big Blue Button
+check=Serververbindung testen
+confirm.delete.meeting=Wollen Sie wirklich den Meeting "{0}" l\u00F6schen?
+confirm.delete.meeting.title=Meeting "{0}" l\u00F6schen
+confirm.delete.template=Wollen Sie wirklich die Vorlage "{0}" l\u00F6schen?
+confirm.delete.template.title=Vorlage "{0}" l\u00F6schen
+connection.failed=Login fehlgeschlagen.
+connection.successful=Login erfolgreich\!
+edit.template=Vorlage "{0}" bearbeiten
+error.prefix=Ein Fehler ist aufgetreten\:
+error.server.raw={1} <small>Schl\u00FCssel\: {0}</small>
+error.start.after.end=Das Datum f\u00FCr das Ende des Meetings darf nicht vor dem Beginn Datum sein.
+guest.policy.always.accept=Always accept
+guest.policy.always.deny=Always deny
+guest.policy.ask.moderator=Ask moderator
+meeting.create.intro=Das Meeting wurde noch nicht er\u00f6ffnet. Teilnehmer k\u00F6nnen den Raum f\u00fcr ein geplantes Meeting ggf. nicht betreten.
+meeting.description=Beschreibung
+meeting.end=Enddatum
+meeting.ended=Meeting ist schon beendet.
+meeting.followupTime=Nachlaufzeit (Min.)
+meeting.go.button=Zum Meeting Raum gehen
+meeting.join.button=Meeting beitreten
+meeting.leadTime=Vorlaufzeit (Min.)
+meeting.name=Name
+meeting.no.template=Standardeinstellungen
+meeting.permanent=Typ
+meeting.permanent.explain=Meetings vom Typ "Dauernd" teilen sich den gleichen Meetingraum in einem Kursbaustein oder einer Gruppe.
+meeting.permanent.on=Dauernd
+meeting.resource=Resource
+meeting.start=Beginndatum
+meeting.start.button=Meeting starten
+meeting.template=Vorlage
+meeting.welcome=Begr\u00FCssung
+meetings.admin.title=Konfiguration
+meetings.past=Alte Meetings
+meetings.title=Meetings
+meetings.upcoming=Zuk\u00FCnftige Meetings
+no.meeting.configured=Es sind noch keine Meetings konfiguriert.
+no.template.configured=Es sind noch keine Vorlage konfiguriert.
+no.upcoming.meetings=Sie haben keine zuk\u00FCnftigen Meetings.
+option.baseurl=URL Big Blue Button Server
+option.baseurl.example=https\://bigbluebutton
+option.bigbluebutton.secret=Secret
+option.bigbluebutton.shared.secret=Shared secret
+option.clean.meetings=Meetings aufra\u00FCmen (Tage)
+option.dont.clean.meetings=Nie
+table.header.permanent=Dauernd
+table.header.system=System
+template.allowModsToUnmuteUsers=Allow moderators to unmute users
+template.allowStartStopRecording=Allow to start / stop recording
+template.autoStartRecording=Auto start recording
+template.description=Beschreibung
+template.guestPolicy=Guest policy
+template.lockSettingsDisableCam=Lock settings disable camera
+template.lockSettingsDisableMic=Lock settings disable micro
+template.lockSettingsDisableNote=Lock settings disable note
+template.lockSettingsDisablePrivateChat=Lock settings disable private chat
+template.lockSettingsDisablePublicChat=Lock settings disable public chat
+template.lockSettingsLockedLayout=Lock settings locked layout
+template.maxParticipants=Max. Teilnehmer
+template.maxParticipants.default=Standard
+template.muteOnStart=Mute on start
+template.name=Name
+template.webcamsOnlyForModerator=Webcams only for moderators
+templates.title=Vorlagen
+warning.template.in.use=Die Vorlage kann nicht gel\u00F6scht werden weil sie nocht benutzt wird.
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
new file mode 100644
index 0000000000000000000000000000000000000000..ae9723675740ad96bcea11f3a45b3269086c0cbf
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
@@ -0,0 +1,78 @@
+#Thu Mar 19 21:29:07 CET 2020
+account.configuration=Configuration
+add.meeting=Add meeting
+add.template=New template
+admin.menu.title=Big Blue Button
+admin.menu.title.alt=Big Blue Button
+bigbluebutton.intro=Intro
+bigbluebutton.module.enabled=Module "Big Blue Button"
+bigbluebutton.module.enabled.for=Enable for
+bigbluebutton.module.enabled.for.courses=Courses
+bigbluebutton.module.enabled.for.groups=Groups
+bigbluebutton.title=Big Blue Button
+check=Check the connection
+confirm.delete.meeting=Do you really want to delete the meeting "{0}"?
+confirm.delete.meeting.title=Delete meeting "{0}"
+confirm.delete.template=Do you really want to delete the template "{0}"?
+confirm.delete.template.title=Delete template "{0}"
+connection.failed=Login failed.
+connection.successful=Login successful\!
+edit.template=Edit template "{0}"
+error.prefix=An error happened\:
+error.server.raw={1} <small>Key {0}</small>
+error.start.after.end=The end date of the meeting must not be before the start date.
+guest.policy.always.accept=Always accept
+guest.policy.always.deny=Always deny
+guest.policy.ask.moderator=Ask moderator
+meeting.create.intro=The meeting has not been opened, yet. Participants are not able to enter the classroom for a meeting.
+meeting.description=Description
+meeting.end=End date
+meeting.ended=Meeting already ended.
+meeting.followupTime=Follow-up (min.)
+meeting.go.button=Go to the meeting room
+meeting.join.button=Join the meeting
+meeting.leadTime=Prep time (min.)
+meeting.name=Name
+meeting.no.template=Default settings
+meeting.permanent=Typ
+meeting.permanent.explain=Permanent meetings will share the same meeting room within a course element or a group.
+meeting.permanent.on=Permanent
+meeting.resource=Resource
+meeting.start=Start date
+meeting.start.button=Start the meeting
+meeting.template=Template
+meeting.welcome=Welcome message
+meetings.admin.title=Configuration
+meetings.past=Old meetings
+meetings.title=Meetings
+meetings.upcoming=Upcoming meetings
+no.meeting.configured=No meetings have been configured yet..
+no.template.configured=No templates have been configured yet..
+no.upcoming.meetings=You don't have any upcoming meeting.
+option.baseurl=URL Big Blue Button Server
+option.baseurl.example=https\://bigbluebutton
+option.bigbluebutton.secret=Secret
+option.bigbluebutton.shared.secret=Shared secret
+option.clean.meetings=Clean-up meetings (days)
+option.dont.clean.meetings=Never
+table.header.permanent=Permanent
+table.header.system=System
+template.allowModsToUnmuteUsers=Allow moderators to unmute users
+template.allowStartStopRecording=Allow to start / stop recording
+template.autoStartRecording=Auto start recording
+template.description=Description
+template.guestPolicy=Guest policy
+template.lockSettingsDisableCam=Lock settings disable camera
+template.lockSettingsDisableMic=Lock settings disable micro
+template.lockSettingsDisableNote=Lock settings disable note
+template.lockSettingsDisablePrivateChat=Lock settings disable private chat
+template.lockSettingsDisablePublicChat=Lock settings disable public chat
+template.lockSettingsLockedLayout=Lock settings locked layout
+template.maxParticipants=Max. participants
+template.maxParticipants.default=Default
+template.muteOnStart=Mute on start
+template.name=Name
+template.webcamsOnlyForModerator=Webcams only for moderators
+templates.title=Templates
+warning.template.in.use=The template cannot be deleted. It's still used.
+
diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
index ebba28502a0248bb51b358b422b23adf0207c086..b9c57387ed3dcce6f85552b2541b95c3bbf25021 100644
--- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
@@ -212,6 +212,10 @@
 					<constructor-arg index="0" value="OLAT_14.2.0" />
 					<property name="alterDbStatements" value="alter_14_1_x_to_14_2_0.sql" />
 				</bean>
+				<bean id="database_upgrade_14_2_5" class="org.olat.upgrade.DatabaseUpgrade">
+					<constructor-arg index="0" value="OLAT_14.2.5" />
+					<property name="alterDbStatements" value="alter_14_2_x_to_14_2_5.sql" />
+				</bean>
 				<bean id="database_upgrade_15_pre_0" class="org.olat.upgrade.DatabaseUpgrade">
 					<constructor-arg index="0" value="OLAT_15.pre.0" />
 					<property name="alterDbStatements" value="alter_14_2_x_to_15_pre_0.sql" />
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index 0a48d59bf8afc7a7268d0d73c15745833b1e26d3..8ebf748198d67823b6ff7c366f5478ffb2c689a9 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -173,6 +173,8 @@
 		<class>org.olat.modules.adobeconnect.model.AdobeConnectUserImpl</class>
 		<class>org.olat.modules.adobeconnect.model.AdobeConnectMeetingImpl</class>
 		<class>org.olat.modules.assessment.model.AssessmentEntryImpl</class>
+		<class>org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl</class>
+		<class>org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingTemplateImpl</class>
 		<class>org.olat.modules.curriculum.model.CurriculumImpl</class>
 		<class>org.olat.modules.curriculum.model.CurriculumElementImpl</class>
 		<class>org.olat.modules.curriculum.model.CurriculumElementTypeImpl</class>
diff --git a/src/main/resources/database/mysql/alter_14_2_x_to_14_2_5.sql b/src/main/resources/database/mysql/alter_14_2_x_to_14_2_5.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5f7ca8b2ecd8d8b816a955f3a65040caf8e75810
--- /dev/null
+++ b/src/main/resources/database/mysql/alter_14_2_x_to_14_2_5.sql
@@ -0,0 +1,53 @@
+create table o_bbb_template (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_system bool default false not null,
+   b_external_id varchar(255) default null,
+   b_max_participants int default null,
+   b_mute_on_start bool default null,
+   b_auto_start_recording bool default null,
+   b_allow_start_stop_recording bool default null,
+   b_webcams_only_for_moderator bool default null,
+   b_allow_mods_to_unmute_users bool default null,
+   b_lock_set_disable_cam bool default null,
+   b_lock_set_disable_mic bool default null,
+   b_lock_set_disable_priv_chat bool default null,
+   b_lock_set_disable_public_chat bool default null,
+   b_lock_set_disable_note bool default null,
+   b_lock_set_locked_layout bool default null,
+   b_guest_policy varchar(32) default null,
+   primary key (id)
+);
+alter table o_bbb_template ENGINE = InnoDB;
+
+create table o_bbb_meeting (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   b_meeting_id varchar(128) not null,
+   b_attendee_pw varchar(128) not null,
+   b_moderator_pw varchar(128) not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_welcome mediumtext,
+   b_permanent bool default false not null,
+   b_start_date datetime default null,
+   b_leadtime bigint default 0 not null,
+   b_start_with_leadtime datetime,
+   b_end_date datetime default null,
+   b_followuptime bigint default 0 not null,
+   b_end_with_followuptime datetime,
+   fk_entry_id bigint default null,
+   a_sub_ident varchar(64) default null,
+   fk_group_id bigint default null,
+   fk_template_id bigint default null,
+   primary key (id)
+);
+alter table o_bbb_meeting ENGINE = InnoDB;
+
+alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
+alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
+alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id);
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 5cd90b4981fe34837a77a6f2e32bd585413f987a..9071df94d85c61b9376071d10556a8cad5cbabed 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1233,6 +1233,56 @@ create table o_aconnect_user (
    primary key (id)
 );
 
+-- Bigbluebutton
+create table o_bbb_template (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_system bool default false not null,
+   b_external_id varchar(255) default null,
+   b_max_participants int default null,
+   b_mute_on_start bool default null,
+   b_auto_start_recording bool default null,
+   b_allow_start_stop_recording bool default null,
+   b_webcams_only_for_moderator bool default null,
+   b_allow_mods_to_unmute_users bool default null,
+   b_lock_set_disable_cam bool default null,
+   b_lock_set_disable_mic bool default null,
+   b_lock_set_disable_priv_chat bool default null,
+   b_lock_set_disable_public_chat bool default null,
+   b_lock_set_disable_note bool default null,
+   b_lock_set_locked_layout bool default null,
+   b_guest_policy varchar(32) default null,
+   primary key (id)
+);
+
+create table o_bbb_meeting (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   b_meeting_id varchar(128) not null,
+   b_attendee_pw varchar(128) not null,
+   b_moderator_pw varchar(128) not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_welcome mediumtext,
+   b_permanent bool default false not null,
+   b_start_date datetime default null,
+   b_leadtime bigint default 0 not null,
+   b_start_with_leadtime datetime,
+   b_end_date datetime default null,
+   b_followuptime bigint default 0 not null,
+   b_end_with_followuptime datetime,
+   fk_entry_id bigint default null,
+   a_sub_ident varchar(64) default null,
+   fk_group_id bigint default null,
+   fk_template_id bigint default null,
+   primary key (id)
+);
+
+
 -- assessment tables
 -- efficiency statments
 create table if not exists o_as_eff_statement (
@@ -3337,6 +3387,8 @@ alter table o_qp_license ENGINE = InnoDB;
 alter table o_om_room_reference ENGINE = InnoDB;
 alter table o_aconnect_meeting ENGINE = InnoDB;
 alter table o_aconnect_user ENGINE = InnoDB;
+alter table o_bbb_template ENGINE = InnoDB;
+alter table o_bbb_meeting ENGINE = InnoDB;
 alter table o_im_message ENGINE = InnoDB;
 alter table o_im_notification ENGINE = InnoDB;
 alter table o_im_roster_entry ENGINE = InnoDB;
@@ -3726,6 +3778,11 @@ alter table o_aconnect_meeting add constraint aconnect_meet_grp_idx foreign key
 
 alter table o_aconnect_user add constraint aconn_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
 
+-- Bigbluebutton
+alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
+alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
+alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id);
+
 -- eportfolio
 alter table o_ep_artefact add constraint FKF26C8375236F28X foreign key (fk_artefact_auth_id) references o_bs_identity (id);
 alter table o_ep_artefact add constraint FKA0070D12316A97B4 foreign key (fk_struct_el_id) references o_ep_struct_el (structure_id);
diff --git a/src/main/resources/database/oracle/alter_14_2_x_to_14_2_5.sql b/src/main/resources/database/oracle/alter_14_2_x_to_14_2_5.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b9a23fcb51c11e762de568b4c286df7efadaf3d3
--- /dev/null
+++ b/src/main/resources/database/oracle/alter_14_2_x_to_14_2_5.sql
@@ -0,0 +1,54 @@
+create table o_bbb_template (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_system number default 0 not null,
+   b_external_id varchar(255) default null,
+   b_max_participants int default null,
+   b_mute_on_start number default null,
+   b_auto_start_recording number default null,
+   b_allow_start_stop_recording number default null,
+   b_webcams_only_for_moderator number default null,
+   b_allow_mods_to_unmute_users number default null,
+   b_lock_set_disable_cam number default null,
+   b_lock_set_disable_mic number default null,
+   b_lock_set_disable_priv_chat number default null,
+   b_lock_set_disable_public_chat number default null,
+   b_lock_set_disable_note number default null,
+   b_lock_set_locked_layout number default null,
+   b_guest_policy varchar(32) default null,
+   primary key (id)
+);
+
+create table o_bbb_meeting (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_meeting_id varchar(128) not null,
+   b_attendee_pw varchar(128) not null,
+   b_moderator_pw varchar(128) not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_welcome CLOB,
+   b_permanent number default 0 not null,
+   b_start_date timestamp default null,
+   b_leadtime number(20) default 0 not null,
+   b_start_with_leadtime timestamp,
+   b_end_date timestamp default null,
+   b_followuptime number(20) default 0 not null,
+   b_end_with_followuptime timestamp,
+   fk_entry_id number(20) default null,
+   a_sub_ident varchar(64) default null,
+   fk_group_id number(20) default null,
+   fk_template_id number(20) default null,
+   primary key (id)
+);
+
+alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
+create index idx_bbb_meet_entry_idx on o_bbb_meeting(fk_entry_id);
+alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
+create index idx_bbb_meet_grp_idx on o_bbb_meeting(fk_group_id);
+alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id);
+create index idx_bbb_meet_template_idx on o_bbb_meeting(fk_template_id);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 968356f30b985c34604a74904822bf16be34d15f..8e5da4dcbc2acaf017744a25e28b0dc2e73edee5 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1298,6 +1298,56 @@ create table o_aconnect_user (
    primary key (id)
 );
 
+-- Bigbluebutton
+create table o_bbb_template (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_system number default 0 not null,
+   b_external_id varchar(255) default null,
+   b_max_participants int default null,
+   b_mute_on_start number default null,
+   b_auto_start_recording number default null,
+   b_allow_start_stop_recording number default null,
+   b_webcams_only_for_moderator number default null,
+   b_allow_mods_to_unmute_users number default null,
+   b_lock_set_disable_cam number default null,
+   b_lock_set_disable_mic number default null,
+   b_lock_set_disable_priv_chat number default null,
+   b_lock_set_disable_public_chat number default null,
+   b_lock_set_disable_note number default null,
+   b_lock_set_locked_layout number default null,
+   b_guest_policy varchar(32) default null,
+   primary key (id)
+);
+
+create table o_bbb_meeting (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_meeting_id varchar(128) not null,
+   b_attendee_pw varchar(128) not null,
+   b_moderator_pw varchar(128) not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_welcome CLOB,
+   b_permanent number default 0 not null,
+   b_start_date timestamp default null,
+   b_leadtime number(20) default 0 not null,
+   b_start_with_leadtime timestamp,
+   b_end_date timestamp default null,
+   b_followuptime number(20) default 0 not null,
+   b_end_with_followuptime timestamp,
+   fk_entry_id number(20) default null,
+   a_sub_ident varchar(64) default null,
+   fk_group_id number(20) default null,
+   fk_template_id number(20) default null,
+   primary key (id)
+);
+
+
 create table o_as_eff_statement (
    id number(20) not null,
    version number(20) not null,
@@ -3754,6 +3804,14 @@ create index idx_aconnect_meet_grp_idx on o_aconnect_meeting(fk_group_id);
 alter table o_aconnect_user add constraint aconn_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
 create index idx_aconn_ident_idx on o_aconnect_user (fk_identity_id);
 
+-- Bigbluebutton
+alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
+create index idx_bbb_meet_entry_idx on o_bbb_meeting(fk_entry_id);
+alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
+create index idx_bbb_meet_grp_idx on o_bbb_meeting(fk_group_id);
+alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id);
+create index idx_bbb_meet_template_idx on o_bbb_meeting(fk_template_id);
+
 -- eportfolio
 alter table o_ep_artefact add constraint FKF26C8375236F28X foreign key (fk_artefact_auth_id) references o_bs_identity (id);
 create index idx_artfeact_to_auth_idx on o_ep_artefact (fk_artefact_auth_id);
diff --git a/src/main/resources/database/postgresql/alter_14_2_x_to_14_2_5.sql b/src/main/resources/database/postgresql/alter_14_2_x_to_14_2_5.sql
new file mode 100644
index 0000000000000000000000000000000000000000..150c4e68f4e76d91ecdd5d2a2b0beeaf8a48fd63
--- /dev/null
+++ b/src/main/resources/database/postgresql/alter_14_2_x_to_14_2_5.sql
@@ -0,0 +1,54 @@
+create table o_bbb_template (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_system bool default false not null,
+   b_external_id varchar(255) default null,
+   b_max_participants int default null,
+   b_mute_on_start bool default null,
+   b_auto_start_recording bool default null,
+   b_allow_start_stop_recording bool default null,
+   b_webcams_only_for_moderator bool default null,
+   b_allow_mods_to_unmute_users bool default null,
+   b_lock_set_disable_cam bool default null,
+   b_lock_set_disable_mic bool default null,
+   b_lock_set_disable_priv_chat bool default null,
+   b_lock_set_disable_public_chat bool default null,
+   b_lock_set_disable_note bool default null,
+   b_lock_set_locked_layout bool default null,
+   b_guest_policy varchar(32) default null,
+   primary key (id)
+);
+
+create table o_bbb_meeting (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_meeting_id varchar(128) not null,
+   b_attendee_pw varchar(128) not null,
+   b_moderator_pw varchar(128) not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_welcome text,
+   b_permanent bool default false not null,
+   b_start_date timestamp default null,
+   b_leadtime bigint default 0 not null,
+   b_start_with_leadtime timestamp,
+   b_end_date timestamp default null,
+   b_followuptime bigint default 0 not null,
+   b_end_with_followuptime timestamp,
+   fk_entry_id int8 default null,
+   a_sub_ident varchar(64) default null,
+   fk_group_id int8 default null,
+   fk_template_id int8 default null,
+   primary key (id)
+);
+
+alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
+create index idx_bbb_meet_entry_idx on o_bbb_meeting(fk_entry_id);
+alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
+create index idx_bbb_meet_grp_idx on o_bbb_meeting(fk_group_id);
+alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id);
+create index idx_bbb_meet_template_idx on o_bbb_meeting(fk_template_id);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index a2425d0eda83facd95828b1baeb27917818983bd..86976814d99b07b02ff5577b4142456cb9fadf49 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1258,6 +1258,55 @@ create table o_aconnect_user (
    primary key (id)
 );
 
+-- Bigbluebutton
+create table o_bbb_template (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_system bool default false not null,
+   b_external_id varchar(255) default null,
+   b_max_participants int default null,
+   b_mute_on_start bool default null,
+   b_auto_start_recording bool default null,
+   b_allow_start_stop_recording bool default null,
+   b_webcams_only_for_moderator bool default null,
+   b_allow_mods_to_unmute_users bool default null,
+   b_lock_set_disable_cam bool default null,
+   b_lock_set_disable_mic bool default null,
+   b_lock_set_disable_priv_chat bool default null,
+   b_lock_set_disable_public_chat bool default null,
+   b_lock_set_disable_note bool default null,
+   b_lock_set_locked_layout bool default null,
+   b_guest_policy varchar(32) default null,
+   primary key (id)
+);
+
+create table o_bbb_meeting (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_meeting_id varchar(128) not null,
+   b_attendee_pw varchar(128) not null,
+   b_moderator_pw varchar(128) not null,
+   b_name varchar(128) not null,
+   b_description varchar(2000) default null,
+   b_welcome text,
+   b_permanent bool default false not null,
+   b_start_date timestamp default null,
+   b_leadtime bigint default 0 not null,
+   b_start_with_leadtime timestamp,
+   b_end_date timestamp default null,
+   b_followuptime bigint default 0 not null,
+   b_end_with_followuptime timestamp,
+   fk_entry_id int8 default null,
+   a_sub_ident varchar(64) default null,
+   fk_group_id int8 default null,
+   fk_template_id int8 default null,
+   primary key (id)
+);
+
 -- efficiency statments
 create table o_as_eff_statement (
    id int8 not null,
@@ -3644,6 +3693,14 @@ create index idx_aconnect_meet_grp_idx on o_aconnect_meeting(fk_group_id);
 alter table o_aconnect_user add constraint aconn_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
 create index idx_aconn_ident_idx on o_aconnect_user (fk_identity_id);
 
+-- Bigbluebutton
+alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
+create index idx_bbb_meet_entry_idx on o_bbb_meeting(fk_entry_id);
+alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
+create index idx_bbb_meet_grp_idx on o_bbb_meeting(fk_group_id);
+alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id);
+create index idx_bbb_meet_template_idx on o_bbb_meeting(fk_template_id);
+
 -- eportfolio
 alter table o_ep_artefact add constraint FKF26C8375236F28X foreign key (fk_artefact_auth_id) references o_bs_identity (id);
 create index idx_artfeact_to_auth_idx on o_ep_artefact (fk_artefact_auth_id);
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index cf69151be1e4ea92a40a416efcfac1f800e2d137..cdc9a1ab3ab9964c7a78e2f850a9ce6ee11a326d 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -1511,6 +1511,19 @@ vc.openmeetings.supportemail=${supportemail}
 vc.gotomeetings.enabled=false
 vc.gotomeetings.consumerKey=
 
+#Big Blue Button
+vc.bigbluebutton.enabled=false
+vc.bigbluebutton.protocol=https
+vc.bigbluebutton.port=443
+vc.bigbluebutton.baseurl=
+vc.bigbluebutton.context=
+vc.bigbluebutton.groups=true
+vc.bigbluebutton.courses=true
+vc.bigbluebutton.cleanupMeetings=false
+vc.bigbluebutton.daysToKeep=
+vc.bigbluebutton.secret=
+vc.bigbluebutton.shared.secret=
+
 ########################################
 # Options for card2brain
 ########################################
diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7cc91da1fb39a75ab45ba3d3964bac4d3d2b85ea
--- /dev/null
+++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java
@@ -0,0 +1,127 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.repository.RepositoryEntry;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonMeetingDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao;
+	
+	@Test
+	public void createMeetingForRepositoryEntry() {
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		String name = "Big blue button - 1";
+		String subIdent = UUID.randomUUID().toString();
+		
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting(name, entry, subIdent, null);
+		dbInstance.commit();
+		Assert.assertNotNull(meeting.getKey());
+		Assert.assertNotNull(meeting.getCreationDate());
+		Assert.assertNotNull(meeting.getLastModified());
+		Assert.assertNotNull(meeting.getMeetingId());
+		Assert.assertNotNull(meeting.getAttendeePassword());
+		Assert.assertNotNull(meeting.getModeratorPassword());
+		Assert.assertEquals(entry, meeting.getEntry());
+		Assert.assertEquals(subIdent, meeting.getSubIdent());
+		Assert.assertNull(meeting.getBusinessGroup());	
+	}
+	
+
+	@Test
+	public void createUpdateMeetingForRepositoryEntry() {
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		String name = "Big blue button - 2";
+		String subIdent = UUID.randomUUID().toString();
+		
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting(name, entry, subIdent, null);
+		dbInstance.commit();
+		meeting.setName("A brand new name");
+		meeting.setDescription("A little description");
+		meeting.setWelcome("Welcome you");
+		meeting.setPermanent(false);
+		meeting.setStartDate(new Date());
+		meeting.setLeadTime(15);
+		meeting.setEndDate(new Date());
+		meeting.setFollowupTime(7);
+		
+		meeting = bigBlueButtonMeetingDao.updateMeeting(meeting);
+		dbInstance.commit();
+		
+		BigBlueButtonMeeting reloadedMeeting = bigBlueButtonMeetingDao.loadByKey(meeting.getKey());
+		
+		Assert.assertEquals(meeting, reloadedMeeting);
+		Assert.assertNotNull(meeting.getCreationDate());
+		Assert.assertNotNull(meeting.getLastModified());
+		Assert.assertEquals(meeting.getMeetingId(), reloadedMeeting.getMeetingId());
+		Assert.assertEquals(meeting.getAttendeePassword(), reloadedMeeting.getAttendeePassword());
+		Assert.assertEquals(meeting.getModeratorPassword(), reloadedMeeting.getModeratorPassword());
+		
+		Assert.assertEquals("A brand new name", reloadedMeeting.getName());
+		Assert.assertEquals("A little description", reloadedMeeting.getDescription());
+		Assert.assertEquals("Welcome you", reloadedMeeting.getWelcome());
+
+		Assert.assertFalse(reloadedMeeting.isPermanent());
+		Assert.assertNotNull(meeting.getStartDate());
+		Assert.assertEquals(15l, reloadedMeeting.getLeadTime());
+		Assert.assertNotNull(meeting.getStartWithLeadTime());
+		Assert.assertNotNull(meeting.getEndDate());
+		Assert.assertEquals(7l, reloadedMeeting.getFollowupTime());
+		Assert.assertNotNull(meeting.getEndWithFollowupTime());
+		
+		Assert.assertNull(reloadedMeeting.getBusinessGroup());
+	}
+	
+	@Test
+	public void getMeetingsByRepositoryEntry() {
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		String name = "Big blue button - 2";
+		String subIdent = UUID.randomUUID().toString();
+		
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting(name, entry, subIdent, null);
+		dbInstance.commit();
+		
+		List<BigBlueButtonMeeting> meetings = bigBlueButtonMeetingDao.getMeetings(entry, subIdent, null);
+		Assert.assertNotNull(meetings);
+		Assert.assertEquals(1, meetings.size());
+		Assert.assertTrue(meetings.contains(meeting));
+	}
+
+}
diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingTemplateDAOTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingTemplateDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec12a1888f12c002d368e43475cec1d839f4169c
--- /dev/null
+++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingTemplateDAOTest.java
@@ -0,0 +1,139 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
+import org.olat.modules.bigbluebutton.GuestPolicyEnum;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingTemplateImpl;
+import org.olat.repository.RepositoryEntry;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 19 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonMeetingTemplateDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao;
+	@Autowired
+	private BigBlueButtonMeetingTemplateDAO bigBlueButtonMeetingTemplateDao;
+	
+	@Test
+	public void createTemplate() {
+		String externalId = UUID.randomUUID().toString();
+		BigBlueButtonMeetingTemplate template = bigBlueButtonMeetingTemplateDao.createTemplate("A new template", externalId, false);
+		dbInstance.commit();
+		
+		Assert.assertNotNull(template.getKey());
+		Assert.assertNotNull(template.getCreationDate());
+		Assert.assertNotNull(template.getLastModified());
+		Assert.assertEquals(externalId, ((BigBlueButtonMeetingTemplateImpl)template).getExternalId());
+	}
+	
+	@Test
+	public void createUpdateTemplate() {
+		String externalId = UUID.randomUUID().toString();
+		BigBlueButtonMeetingTemplate template = bigBlueButtonMeetingTemplateDao.createTemplate("A new template to update", externalId, false);
+		dbInstance.commit();
+		
+		template.setMaxParticipants(99);
+		template.setMuteOnStart(Boolean.TRUE);
+		template.setAutoStartRecording(Boolean.FALSE);
+		template.setAllowStartStopRecording(Boolean.TRUE);
+		template.setWebcamsOnlyForModerator(Boolean.FALSE);
+		template.setAllowModsToUnmuteUsers(Boolean.TRUE);
+		template.setLockSettingsDisableCam(Boolean.FALSE);
+		template.setLockSettingsDisableMic(Boolean.TRUE);
+		template.setLockSettingsDisablePrivateChat(Boolean.FALSE);
+		template.setLockSettingsDisablePublicChat(Boolean.TRUE);
+		template.setLockSettingsDisableNote(Boolean.FALSE);
+		template.setLockSettingsLockedLayout(Boolean.TRUE);
+		template.setGuestPolicyEnum(GuestPolicyEnum.ASK_MODERATOR);
+		
+		BigBlueButtonMeetingTemplate updatedTemplate = bigBlueButtonMeetingTemplateDao.updateTemplate(template);
+		dbInstance.commitAndCloseSession();
+		
+		BigBlueButtonMeetingTemplate reloadedTemplate = bigBlueButtonMeetingTemplateDao.getTemplate(updatedTemplate);
+		Assert.assertNotNull(reloadedTemplate);
+		Assert.assertEquals(Integer.valueOf(99), reloadedTemplate.getMaxParticipants());
+		Assert.assertEquals(Boolean.TRUE, reloadedTemplate.getMuteOnStart());
+		Assert.assertEquals(Boolean.FALSE, reloadedTemplate.getAutoStartRecording());
+		Assert.assertEquals(Boolean.TRUE, reloadedTemplate.getAllowStartStopRecording());
+		Assert.assertEquals(Boolean.FALSE, reloadedTemplate.getWebcamsOnlyForModerator());
+		Assert.assertEquals(Boolean.TRUE, reloadedTemplate.getAllowModsToUnmuteUsers());
+		Assert.assertEquals(Boolean.FALSE, reloadedTemplate.getLockSettingsDisableCam());
+		Assert.assertEquals(Boolean.TRUE, reloadedTemplate.getLockSettingsDisableMic());
+		Assert.assertEquals(Boolean.FALSE, reloadedTemplate.getLockSettingsDisablePrivateChat());
+		Assert.assertEquals(Boolean.TRUE, reloadedTemplate.getLockSettingsDisablePublicChat());
+		Assert.assertEquals(Boolean.FALSE, reloadedTemplate.getLockSettingsDisableNote());
+		Assert.assertEquals(Boolean.TRUE, reloadedTemplate.getLockSettingsLockedLayout());
+		Assert.assertEquals(GuestPolicyEnum.ASK_MODERATOR, reloadedTemplate.getGuestPolicyEnum());
+	}
+	
+	@Test
+	public void getTemplates() {
+		String externalId = UUID.randomUUID().toString();
+		BigBlueButtonMeetingTemplate template = bigBlueButtonMeetingTemplateDao.createTemplate("A new template", externalId, false);
+		dbInstance.commit();
+		
+		List<BigBlueButtonMeetingTemplate> templates = bigBlueButtonMeetingTemplateDao.getTemplates();
+		Assert.assertNotNull(templates);
+		Assert.assertTrue(templates.contains(template));
+	}
+	
+	@Test
+	public void isTemplateInNotUse() {
+		String externalId = UUID.randomUUID().toString();
+		BigBlueButtonMeetingTemplate template = bigBlueButtonMeetingTemplateDao.createTemplate("A new template", externalId, false);
+		dbInstance.commit();
+		
+		boolean inUse = bigBlueButtonMeetingTemplateDao.isTemplateInUse(template);
+		Assert.assertFalse(inUse);
+	}
+	
+	@Test
+	public void isTemplateInUse() {
+		// make a template and use it in a meeting
+		BigBlueButtonMeetingTemplate template = bigBlueButtonMeetingTemplateDao.createTemplate("A new template", UUID.randomUUID().toString(), false);
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("Big blue button templated - 10", entry, UUID.randomUUID().toString(), null);
+		meeting.setTemplate(template);
+		bigBlueButtonMeetingDao.updateMeeting(meeting);
+		dbInstance.commitAndCloseSession();
+		
+		boolean inUse = bigBlueButtonMeetingTemplateDao.isTemplateInUse(template);
+		Assert.assertTrue(inUse);
+	}
+
+}
diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUriBuilderTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUriBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ad26afff1712f2c5cb1ad2775077d82d866b310
--- /dev/null
+++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUriBuilderTest.java
@@ -0,0 +1,68 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton.manager;
+
+import java.net.URI;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * 
+ * Initial date: 18 mars 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonUriBuilderTest {
+	
+	@Test
+	public void createCheckSummedUrl() {
+		// createname=Test+Meeting&meetingID=abc123&attendeePW=111222&moderatorPW=333444
+		String sharedSecret = "639259d4-9dd8-4b25-bf01-95f9567eaf4b";
+		BigBlueButtonUriBuilder builder = BigBlueButtonUriBuilder
+				.fromUri(URI.create("https://bbb.openolat.org/bigbluebutton/"), sharedSecret);
+		
+		builder.operation("create")
+		       .parameter("name", "Test Meeting")
+		       .parameter("meetingID", "abc123")
+		       .parameter("attendeePW", "111222")
+		       .parameter("moderatorPW", "333444");
+		
+		String url = builder.build().toString();
+		Assert.assertEquals("https://bbb.openolat.org/bigbluebutton/api/create?name=Test+Meeting&meetingID=abc123&attendeePW=111222&moderatorPW=333444&checksum=da9185f7f333cfdfcd6eeac32dca3777510c4c436020d8b887ba5515bd1d189e" , url);
+	}
+	
+	@Test
+	public void createCheckSummedSpecialCharactersUrl() {
+		String sharedSecret = "639259d4-9dd8-4b25-bf01-95f9567eaf4b";
+		BigBlueButtonUriBuilder builder = BigBlueButtonUriBuilder
+				.fromUri(URI.create("https://bbb.openolat.org/bigbluebutton/"), sharedSecret);
+		
+		builder.operation("create")
+		       .parameter("name", "Test \u00E9v\u00E9nement")
+		       .parameter("meetingID", "abc123")
+		       .parameter("attendeePW", "111222")
+		       .parameter("moderatorPW", "333444");
+		
+		String url = builder.build().toString();
+		Assert.assertEquals("https://bbb.openolat.org/bigbluebutton/api/create?name=Test+%C3%A9v%C3%A9nement&meetingID=abc123&attendeePW=111222&moderatorPW=333444&checksum=bedc6c6bf6edf42c5ac0f2f3c34cbc8283435029edfa590f43c670e18c4324f9", url);
+	}
+
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 3f6013b245b892a64103e24049cb255bf61a7dee..9227622f1f7eaee224a03b26b3af29c1c1ddc61a 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -205,6 +205,9 @@ import org.junit.runners.Suite;
 	org.olat.modules.adobeconnect.manager.AdobeConnectUserDAOTest.class,
 	org.olat.modules.adobeconnect.manager.AdobeConnectMeetingDAOTest.class,
 	org.olat.modules.adobeconnect.manager.AdobeConnectUtilsTest.class,
+	org.olat.modules.bigbluebutton.manager.BigBlueButtonMeetingDAOTest.class,
+	org.olat.modules.bigbluebutton.manager.BigBlueButtonMeetingTemplateDAOTest.class,
+	org.olat.modules.bigbluebutton.manager.BigBlueButtonUriBuilderTest.class,
 	org.olat.modules.iq.IQManagerTest.class,
 	org.olat.modules.fo.ForumManagerTest.class,//fail
 	org.olat.modules.wiki.WikiUnitTest.class,