diff --git a/src/main/java/org/olat/commons/calendar/CalendarManager.java b/src/main/java/org/olat/commons/calendar/CalendarManager.java
index 8c5776c51d1ac1b3d209d4801fd7a17aac362400..96489b3131b98dc6cd21d16861b18df97ef79bb3 100644
--- a/src/main/java/org/olat/commons/calendar/CalendarManager.java
+++ b/src/main/java/org/olat/commons/calendar/CalendarManager.java
@@ -63,6 +63,7 @@ public interface CalendarManager {
 	public static final String ICAL_X_OLAT_EXTERNAL_ID = "X-OLAT-EXTERNAL-ID";
 	public static final String ICAL_X_OLAT_EXTERNAL_SOURCE = "X-OLAT-EXTERNAL-SOURCE";
 	public static final String ICAL_X_OLAT_VIDEO_STREAM_URL = "X-OLAT-VIDEO-STREAM-URL";
+	public static final String ICAL_X_OLAT_VIDEO_STREAM_URL_TEMPLATE_KEY = "X-OLAT-VIDEO-STREAM-URL_TEMPLATE_KEY";
 
 	/** path prefix for personal iCal feed **/
 	public static final String ICAL_PREFIX_AGGREGATED = "/paggregated/";
diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties
index 81bb551c4c1be4ad12077a52ba899d892ec4f6d4..7359743747e95c0bd399111cbd9eb780feacea1c 100644
--- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties
@@ -120,6 +120,9 @@ cal.links.submit=Speichern
 cal.links.title=Verkn\u00FCpfung
 cal.list=Kalenderliste
 cal.live.stream.url=URL Live Stream
+cal.live.stream.url.template=URL Live Stream
+cal.live.stream.url.type=Live Stream Auswahl
+cal.live.stream.url.type.manually=Manuell
 cal.managecalendars=Kalender importieren und verwalten 
 cal.mon=Montag
 cal.month=Monat
@@ -187,7 +190,7 @@ delete.future=Alle zuk\u00FCnftige Termine l\u00F6schen
 delete.all=Alle Termine l\u00F6schen
 delete.one=Nur die Okkurenz l\u00F6schen
 error.goto.date=Falsche Datumseingabe (dd.mm.yyyy)
-error.atleast.one=Mindestens ein Kalender ausw\u00e4hlen
+error.atleast.one=Mindestens einen Kalender ausw\u00E4hlen
 menu.admin.calendar=Kalender
 menu.admin.calendar.alt=Kalender
 month.long.apr=$org.olat.core.gui.components.form.flexible.impl.elements\:month.long.apr
diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties
index b543b8b3e1c5502e9cb2859dd7e74d15cb4e4a01..1a07dc0e699d3d4cc0aff89fb96c05afb1c40d29 100644
--- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties
@@ -120,6 +120,9 @@ cal.links.submit=Save
 cal.links.title=Link
 cal.list=List of calendars
 cal.live.stream.url=URL live stream
+cal.live.stream.url.template=URL live stream
+cal.live.stream.url.type=Live stream selection
+cal.live.stream.url.type.manually=Manually
 cal.managecalendars=Import and manage calendars 
 cal.mon=Monday
 cal.month=Month
diff --git a/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java
index f618b0331b96a9d0a19220ec517d6dc0c2b7faa0..bb1e9578ee8375f0b1838321e6cc6c359d4cbf8e 100644
--- a/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java
+++ b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java
@@ -701,6 +701,9 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea
 		if(StringHelper.containsNonWhitespace(kEvent.getLiveStreamUrl())) {
 			vEventProperties.add(new XProperty(ICAL_X_OLAT_VIDEO_STREAM_URL, kEvent.getLiveStreamUrl()));
 		}
+		if (kEvent.getLiveStreamUrlTemplateKey() != null) {
+			vEventProperties.add(new XProperty(ICAL_X_OLAT_VIDEO_STREAM_URL_TEMPLATE_KEY, kEvent.getLiveStreamUrlTemplateKey().toString()));
+		}
 		
 		return vEvent;
 	}
@@ -889,6 +892,10 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea
 			calEvent.setLiveStreamUrl(liveStreamUrl.getValue());
 		}
 		
+		Property liveStreamUrlTemplateKey = event.getProperty(ICAL_X_OLAT_VIDEO_STREAM_URL_TEMPLATE_KEY);
+		if (liveStreamUrlTemplateKey != null)
+			calEvent.setLiveStreamUrlTemplateKey(Long.parseLong(liveStreamUrlTemplateKey.getValue()));
+		
 		return calEvent;
 	}
 
@@ -1130,9 +1137,7 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea
 	@Override
 	public boolean updateEventFrom(final Kalendar cal, final KalendarEvent kalendarEvent) {
 		OLATResourceable calOres = getOresHelperFor(cal);
-		Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, () -> {
-			return updateEventAlreadyInSync(cal, kalendarEvent);
-		});
+		Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, () -> updateEventAlreadyInSync(cal, kalendarEvent));
 		return updatedSuccessful.booleanValue();
     }
 
diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java b/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java
index 4112c8502368593f7c956a3f84e347910018efb5..1ff720ed7fa5043127198d2ff6089f362d740542 100644
--- a/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java
+++ b/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java
@@ -76,6 +76,7 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> {
 	private String recurrenceRule;
 	private String recurrenceExc;
 	
+	private Long liveStreamUrlTemplateKey;
 	private String liveStreamUrl;
 	
 	private String externalId;
@@ -388,6 +389,14 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> {
 		this.recurrenceExc = recurrenceExc;
 	}
 	
+	public Long getLiveStreamUrlTemplateKey() {
+		return liveStreamUrlTemplateKey;
+	}
+
+	public void setLiveStreamUrlTemplateKey(Long liveStreamUrlTemplateKey) {
+		this.liveStreamUrlTemplateKey = liveStreamUrlTemplateKey;
+	}
+
 	public String getLiveStreamUrl() {
 		return liveStreamUrl;
 	}
diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java b/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java
index 4c7846d9ea95e96a5d75425cc9874accf55eac69..a4c23f677e1a8bc839f6c5f78867629ebd07c76e 100644
--- a/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java
+++ b/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java
@@ -78,6 +78,7 @@ public class KalendarRecurEvent extends KalendarEvent {
 		setParticipants(event.getParticipants());
 		setRecurrenceExc(event.getRecurrenceExc());
 		setRecurrenceRule(event.getRecurrenceRule());
+		setLiveStreamUrlTemplateKey(event.getLiveStreamUrlTemplateKey());
 		setLiveStreamUrl(event.getLiveStreamUrl());
 		setSourceNodeId(event.getSourceNodeId());
 		setSubject(event.getSubject());
diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java
index 28d4a10eb41f96ed44b6b6ae128e2dc36b17cf2a..ccc5873ae76866cf6c0d1fbf39aa49ced649b38f 100644
--- a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java
+++ b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java
@@ -25,7 +25,10 @@
 
 package org.olat.commons.calendar.ui;
 
+import static org.olat.core.util.ArrayHelper.emptyStrings;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Iterator;
@@ -41,6 +44,7 @@ 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.FormLink;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
@@ -49,12 +53,15 @@ 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.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.logging.OLATRuntimeException;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
+import org.olat.course.nodes.livestream.LiveStreamService;
+import org.olat.course.nodes.livestream.model.UrlTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 
 
@@ -68,6 +75,8 @@ public class CalendarEntryForm extends FormBasicController {
 	private StaticTextElement calendarName;
 	private SingleSelection chooseCalendar;
 	private TextElement subjectEl, descriptionEl, locationEl, liveStreamUrlEl;
+	private MultipleSelectionElement liveStreamUrlTypeEl;
+	private SingleSelection liveStreamUrlTemplateEl;
 	private SelectionElement allDayEvent;
 	
 	private DateChooser begin, end;
@@ -88,6 +97,8 @@ public class CalendarEntryForm extends FormBasicController {
 	
 	@Autowired
 	private CalendarManager calendarManager;
+	@Autowired
+	private LiveStreamService liveStreamService;
 	
 	/**
 	 * Display an event for modification or to add a new event.
@@ -200,7 +211,7 @@ public class CalendarEntryForm extends FormBasicController {
 			}
 		}
 		
-		liveStreamUrlEl.setValue(kalendarEvent.getLiveStreamUrl());
+		updateLiveStreamUI(kalendarEvent);
 	}
 	
 	@Override
@@ -277,7 +288,12 @@ public class CalendarEntryForm extends FormBasicController {
 			event.setRecurrenceRule(rrule);
 		}
 		
-		// live Stream
+		// Live Stream
+		event.setLiveStreamUrlTemplateKey(null);
+		if (liveStreamUrlTemplateEl.isVisible() && liveStreamUrlTemplateEl.isEnabled() && liveStreamUrlTemplateEl.isOneSelected()) {
+			event.setLiveStreamUrlTemplateKey(Long.valueOf(liveStreamUrlTemplateEl.getSelectedKey()));
+			event.setLiveStreamUrl(getLiveStreamUrlFromSelection());
+		}
 		if (liveStreamUrlEl.isVisible() && liveStreamUrlEl.isEnabled()) {
 			String liveStreamUrl = StringHelper.containsNonWhitespace(liveStreamUrlEl.getValue())
 					? liveStreamUrlEl.getValue()
@@ -395,9 +411,14 @@ public class CalendarEntryForm extends FormBasicController {
 		recurrenceEnd.setEnabled(!managedDates);
 		recurrenceEnd.setVisible(!chooseRecurrence.getSelectedKey().equals(RECURRENCE_NONE));
 		
+		liveStreamUrlTypeEl = uifactory.addCheckboxesHorizontal("cal.live.stream.url.type", formLayout, emptyStrings(), emptyStrings());
+		liveStreamUrlTypeEl.addActionListener(FormEvent.ONCHANGE);
+		
+		liveStreamUrlTemplateEl = uifactory.addDropdownSingleselect("cal.live.stream.url.template", formLayout, emptyStrings(), emptyStrings());
+		liveStreamUrlTemplateEl.addActionListener(FormEvent.ONCHANGE);
+		
 		liveStreamUrlEl = uifactory.addTextElement("cal.live.stream.url", 2000, event.getLiveStreamUrl(), formLayout);
-		liveStreamUrlEl.setEnabled(!CalendarManagedFlag.isManaged(event, CalendarManagedFlag.liveStreamUrl));
-		liveStreamUrlEl.setVisible(CalendarController.CALLER_LIVE_STREAM.equals(caller));
+		updateLiveStreamUI(event);
 		
 		classification = uifactory.addRadiosVertical("classification", "cal.form.class", formLayout, classKeys, classValues);
 		classification.setHelpUrlForManualPage("Calendar#_visibility");
@@ -436,6 +457,59 @@ public class CalendarEntryForm extends FormBasicController {
 			deleteEventButton.setElementCssClass("o_sel_cal_delete");
 		}
 	}
+	
+	private void updateLiveStreamUI(KalendarEvent kalendarEvent) {
+		boolean isLiveStream = CalendarController.CALLER_LIVE_STREAM.equals(caller);
+		boolean liveStreamNotManaged = !CalendarManagedFlag.isManaged(kalendarEvent, CalendarManagedFlag.liveStreamUrl);
+		
+		liveStreamUrlTypeEl.setVisible(isLiveStream && liveStreamNotManaged);
+		liveStreamUrlTemplateEl.setVisible(isLiveStream && liveStreamNotManaged);
+		liveStreamUrlEl.setVisible(isLiveStream);
+		liveStreamUrlEl.setEnabled(liveStreamNotManaged);
+		
+		liveStreamUrlEl.setValue(kalendarEvent.getLiveStreamUrl());
+
+		boolean liveStreamUrlManually = true;
+		if (liveStreamUrlTypeEl.isVisible()) {
+			List<UrlTemplate> urlTemplates = liveStreamService.getAllUrlTemplates();
+			if (urlTemplates.isEmpty()) {
+				liveStreamUrlTypeEl.setVisible(false);
+				liveStreamUrlTemplateEl.setVisible(false);
+				return;
+			}
+			
+			KeyValues typeKV = new KeyValues();
+			typeKV.add(KeyValues.entry("key", translate("cal.live.stream.url.type.manually")));
+			KeyValues urlTemplateKV = new KeyValues();
+			liveStreamUrlTypeEl.setKeysAndValues(typeKV.keys(), typeKV.values());
+			
+			urlTemplates.forEach(urlTemplate -> urlTemplateKV.add(KeyValues.entry(urlTemplate.getKey().toString(), urlTemplate.getName())));
+			urlTemplateKV.sort(KeyValues.VALUE_ASC);
+			liveStreamUrlTemplateEl.setKeysAndValues(urlTemplateKV.keys(), urlTemplateKV.values(), null);
+			liveStreamUrlTemplateEl.select(liveStreamUrlTemplateEl.getKey(0), true);
+			
+			Long urlTemplateKey = kalendarEvent.getLiveStreamUrlTemplateKey();
+			if (isValidUrlTemplateKey(urlTemplateKey)) {
+				liveStreamUrlTemplateEl.select(urlTemplateKey.toString(), true);
+				liveStreamUrlManually = false;
+			} else if (!StringHelper.containsNonWhitespace(kalendarEvent.getLiveStreamUrl())) {
+				liveStreamUrlTemplateEl.select(liveStreamUrlTemplateEl.getKey(0), true);
+				liveStreamUrlManually = false;
+			}
+			liveStreamUrlTypeEl.select(liveStreamUrlTypeEl.getKey(0), liveStreamUrlManually);
+		}
+		updateLiveStreamUI(liveStreamUrlManually);
+	}
+
+	private boolean isValidUrlTemplateKey(Long urlTemplateKey) {
+		return urlTemplateKey != null 
+				&& Arrays.stream(liveStreamUrlTemplateEl.getKeys()).anyMatch(key -> key.equals(urlTemplateKey.toString()));
+	}
+	
+	private void updateLiveStreamUI(boolean manually) {
+		liveStreamUrlTemplateEl.setVisible(!manually);
+		liveStreamUrlEl.setVisible(manually);
+	}
 
 	@Override
 	protected void formInnerEvent (UserRequest ureq, FormItem source, FormEvent e) {
@@ -445,11 +519,28 @@ public class CalendarEntryForm extends FormBasicController {
 			boolean allDay = allDayEvent.isSelected(0);
 			begin.setDateChooserTimeEnabled(!allDay);
 			end.setDateChooserTimeEnabled(!allDay);
+		} else if (source == liveStreamUrlTypeEl) {
+			boolean manually = liveStreamUrlTypeEl.isAtLeastSelected(1);
+			updateLiveStreamUI(manually);
+		} else if (source == liveStreamUrlTemplateEl) {
+			doSyncLiveStreamUrl();
 		} else if(deleteEventButton == source) {
 			fireEvent(ureq, new Event("delete"));
 		}
 	}
-	
+
+	private void doSyncLiveStreamUrl() {
+		String liveStreamUrl = getLiveStreamUrlFromSelection();
+		liveStreamUrlEl.setValue(liveStreamUrl);
+	}
+
+	private String getLiveStreamUrlFromSelection() {
+		Long key = Long.valueOf(liveStreamUrlTemplateEl.getSelectedKey());
+		UrlTemplate urlTemplate = liveStreamService.getUrlTemplate(key);
+		String url = liveStreamService.concatUrls(urlTemplate);
+		return StringHelper.containsNonWhitespace(url) ? url : null;
+	}
+
 	@Override
 	protected void doDispose() {
 		//
diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java
index 31209f23055f2929fda9023c7261b6d1d1f321e4..514766d228e9e7bbc760ef66bbf8eba96d1950b7 100644
--- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java
+++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java
@@ -25,6 +25,7 @@ import java.util.concurrent.ScheduledExecutorService;
 
 import org.olat.core.id.Identity;
 import org.olat.course.nodes.cal.CourseCalendars;
+import org.olat.course.nodes.livestream.model.UrlTemplate;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 
@@ -64,6 +65,24 @@ public interface LiveStreamService {
 	 * @return
 	 */
 	Long getLaunchers(RepositoryEntryRef courseEntry, String subIdent, Date from, Date to);
+	
+	UrlTemplate createUrlTemplate(String name);
+	
+	UrlTemplate updateUrlTemplate(UrlTemplate urlTemplate);
+
+	List<UrlTemplate> getAllUrlTemplates();
+
+	UrlTemplate getUrlTemplate(Long key);
+	
+	void deleteUrlTemplate(UrlTemplate urlTemplate);
+	
+	/**
+	 * Concatenate the urls of the template
+	 *
+	 * @param urlTemplate
+	 * @return
+	 */
+	String concatUrls(UrlTemplate urlTemplate);
 
 	/**
 	 * Split the url to separate urls
@@ -80,5 +99,5 @@ public interface LiveStreamService {
 	 * @return
 	 */
 	String[] getStreamingUrls(String[] urls);
-
+	
 }
diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java b/src/main/java/org/olat/course/nodes/livestream/manager/LaunchDAO.java
similarity index 98%
rename from src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java
rename to src/main/java/org/olat/course/nodes/livestream/manager/LaunchDAO.java
index 922a6bf5fe1ea54eae4306b684cdb813c73a4887..46c75b24846b48d8e50bf838d0087dd6dd302fc0 100644
--- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java
+++ b/src/main/java/org/olat/course/nodes/livestream/manager/LaunchDAO.java
@@ -42,7 +42,7 @@ import org.springframework.stereotype.Component;
  *
  */
 @Component
-public class LiveStreamLaunchDAO {
+public class LaunchDAO {
 
 	@Autowired
 	private DB dbInstance;
diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java
index 3c5839ec3999ec8bb03cc49b54525bc4119c8290..b2a8ca4c332cfb7dec6660022019672fa2698a65 100644
--- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java
+++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java
@@ -49,6 +49,7 @@ import org.olat.course.nodes.livestream.LiveStreamEvent;
 import org.olat.course.nodes.livestream.LiveStreamModule;
 import org.olat.course.nodes.livestream.LiveStreamService;
 import org.olat.course.nodes.livestream.model.LiveStreamEventImpl;
+import org.olat.course.nodes.livestream.model.UrlTemplate;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.DisposableBean;
@@ -74,7 +75,9 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 	@Autowired
 	private CalendarManager calendarManager;
 	@Autowired
-	private LiveStreamLaunchDAO launchDao;
+	private UrlTemplateDAO urlTemplateDao;
+	@Autowired
+	private LaunchDAO launchDao;
 
 	@Override
 	public ScheduledExecutorService getScheduler() {
@@ -107,7 +110,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 		cTo.add(Calendar.MINUTE, bufferBeforeMin);
 		Date to = cTo.getTime();
 		
-		return getLiveStreamEvents(calendars, from, to);
+		return getLiveStreamEvents(calendars, from, to, true);
 	}
 
 	@Override
@@ -121,7 +124,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 		cTo.add(Calendar.MINUTE, bufferBeforeMin);
 		Date to = cTo.getTime();
 		
-		return getLiveStreamEvents(calendars, from, to);
+		return getLiveStreamEvents(calendars, from, to, false);
 	}
 	
 	@Override
@@ -136,18 +139,16 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 		cTo.add(Calendar.YEAR, 10);
 		Date to = cTo.getTime();
 		
-		return getLiveStreamEvents(calendars, from, to).stream()
+		return getLiveStreamEvents(calendars, from, to, false).stream()
 				.filter(notStartedFilter(from))
 				.collect(Collectors.toList());
 	}
 
 	private Predicate<LiveStreamEvent> notStartedFilter(Date from) {
-		return (LiveStreamEvent e) -> {
-			return !e.getBegin().before(from);
-			};
+		return (LiveStreamEvent e) -> !e.getBegin().before(from);
 	}
 
-	private List<? extends LiveStreamEvent> getLiveStreamEvents(CourseCalendars calendars, Date from, Date to) {
+	private List<? extends LiveStreamEvent> getLiveStreamEvents(CourseCalendars calendars, Date from, Date to, boolean syncUrl) {
 		List<LiveStreamEvent> liveStreamEvents = new ArrayList<>();
 		for (KalendarRenderWrapper cal : calendars.getCalendars()) {
 			if(cal != null) {
@@ -160,9 +161,9 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 					
 					if (isLiveStream(event)) {
 						boolean timeOnly = !privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_X_FREEBUSY;
-						LiveStreamEventImpl liveStreamEvent = toLiveStreamEvent(event, timeOnly);
+						LiveStreamEventImpl liveStreamEvent = toLiveStreamEvent(event, timeOnly, syncUrl);
 						liveStreamEvents.add(liveStreamEvent);
-					};
+					}
 				}
 			}
 		}
@@ -174,7 +175,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 		return event.getLiveStreamUrl() != null;
 	}
 
-	private LiveStreamEventImpl toLiveStreamEvent(KalendarEvent event, boolean timeOnly) {
+	private LiveStreamEventImpl toLiveStreamEvent(KalendarEvent event, boolean timeOnly, boolean syncUrl) {
 		LiveStreamEventImpl liveStreamEvent = new LiveStreamEventImpl();
 		liveStreamEvent.setId(event.getID());
 		liveStreamEvent.setAllDayEvent(event.isAllDayEvent());
@@ -182,6 +183,14 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 		Date end = CalendarUtils.endOf(event);
 		liveStreamEvent.setEnd(end);
 		liveStreamEvent.setLiveStreamUrl(event.getLiveStreamUrl());
+		if (syncUrl && event.getLiveStreamUrlTemplateKey() != null) {
+			Long key = Long.valueOf(event.getLiveStreamUrlTemplateKey());
+			UrlTemplate urlTemplate = urlTemplateDao.loadByKey(key);
+			String concatUrls = concatUrls(urlTemplate);
+			if (StringHelper.containsNonWhitespace(concatUrls)) {
+				liveStreamEvent.setLiveStreamUrl(concatUrls);
+			}
+		}
 		if (!timeOnly) {
 			liveStreamEvent.setSubject(event.getSubject());
 			liveStreamEvent.setDescription(event.getDescription());
@@ -200,6 +209,41 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 		return launchDao.getLaunchers(courseEntry, subIdent, from, to);
 	}
 
+	@Override
+	public UrlTemplate createUrlTemplate(String name) {
+		return urlTemplateDao.create(name);
+	}
+
+	@Override
+	public UrlTemplate updateUrlTemplate(UrlTemplate urlTemplate) {
+		return urlTemplateDao.update(urlTemplate);
+	}
+
+	@Override
+	public List<UrlTemplate> getAllUrlTemplates() {
+		return urlTemplateDao.loadAll();
+	}
+
+	@Override
+	public UrlTemplate getUrlTemplate(Long key) {
+		return urlTemplateDao.loadByKey(key);
+	}
+
+	@Override
+	public void deleteUrlTemplate(UrlTemplate urlTemplate) {
+		urlTemplateDao.delete(urlTemplate);
+	}
+
+	@Override
+	public String concatUrls(UrlTemplate urlTemplate) {
+		if (urlTemplate == null) return null;
+		
+		String urls = List.of(urlTemplate.getUrl1(), urlTemplate.getUrl2()).stream()
+				.filter(StringHelper::containsNonWhitespace)
+				.collect(Collectors.joining(liveStreamModule.getUrlSeparator()));
+		return StringHelper.containsNonWhitespace(urls)? urls: null;
+	}
+	
 	@Override
 	public String[] splitUrl(String url) {
 		if (!StringHelper.containsNonWhitespace(url)) return new String[0];
@@ -239,7 +283,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean
 				}
 			}
 		} catch(Exception e) {
-			log.error("", e);
+			log.debug("LiveStream not available.", e);
 		}
 		
 		return false;
diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java b/src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fe26760e75d662d691692386572e851b4a92fa3
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java
@@ -0,0 +1,93 @@
+/**
+ * <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.livestream.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.course.nodes.livestream.model.UrlTemplate;
+import org.olat.course.nodes.livestream.model.UrlTemplateImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Component
+public class UrlTemplateDAO {
+	
+	@Autowired
+	private DB dbInstance;
+
+	public UrlTemplate create(String name) {
+		UrlTemplateImpl urlTemplate = new UrlTemplateImpl();
+		urlTemplate.setCreationDate(new Date());
+		urlTemplate.setLastModified(urlTemplate.getCreationDate());
+		urlTemplate.setName(name);
+		dbInstance.getCurrentEntityManager().persist(urlTemplate);
+		return urlTemplate;
+	}
+
+	public UrlTemplate update(UrlTemplate urlTemplate) {
+		if (urlTemplate instanceof UrlTemplateImpl) {
+			UrlTemplateImpl impl = (UrlTemplateImpl)urlTemplate;
+			impl.setLastModified(new Date());
+			dbInstance.getCurrentEntityManager().merge(impl);
+		}
+		return urlTemplate;
+	}
+	
+	public List<UrlTemplate> loadAll() {
+		String query = "select urlTemplate from livestreamurltemplate urlTemplate";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(query, UrlTemplate.class)
+				.getResultList();
+	}
+
+	public UrlTemplate loadByKey(Long key) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select urlTemplate");
+		sb.append("  from livestreamurltemplate urlTemplate");
+		sb.and().append("urlTemplate.key = :key");
+		
+		List<UrlTemplate> urlTemplates  = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), UrlTemplate.class)
+				.setParameter("key", key)
+				.getResultList();
+		return !urlTemplates.isEmpty()? urlTemplates.get(0): null;
+	}
+
+	public void delete(UrlTemplate urlTemplate) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("delete from livestreamurltemplate urlTemplate");
+		sb.and().append("urlTemplate.key = :key");
+		
+		dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString())
+				.setParameter("key", urlTemplate.getKey())
+				.executeUpdate();
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.java b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.java
new file mode 100644
index 0000000000000000000000000000000000000000..e597a1c659d1f28e3ab88a6d761a0899c23459a5
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.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.course.nodes.livestream.model;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface UrlTemplate {
+
+	Long getKey();
+
+	String getName();
+
+	void setName(String name);
+
+	String getUrl1();
+
+	void setUrl1(String url1);
+
+	String getUrl2();
+
+	void setUrl2(String url2);
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6ab358c173a4b2cc578983a70b99a9bded3d6de
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java
@@ -0,0 +1,152 @@
+/**
+ * <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.livestream.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.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="livestreamurltemplate")
+@Table(name="o_livestream_url_template")
+public class UrlTemplateImpl implements UrlTemplate, Persistable, CreateInfo, ModifiedInfo {
+	
+	private static final long serialVersionUID = -8987169701865579049L;
+
+	@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="l_name", nullable=false, insertable=true, updatable=true)
+	private String name;
+	@Column(name="l_url1", nullable=true, insertable=true, updatable=true)
+	private String url1;
+	@Column(name="l_url2", nullable=true, insertable=true, updatable=true)
+	private String url2;
+	
+	@Override
+	public Long getKey() {
+		return 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 getUrl1() {
+		return url1;
+	}
+
+	@Override
+	public void setUrl1(String url1) {
+		this.url1 = url1;
+	}
+
+	@Override
+	public String getUrl2() {
+		return url2;
+	}
+
+	@Override
+	public void setUrl2(String url2) {
+		this.url2 = url2;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((key == null) ? 0 : key.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		UrlTemplateImpl other = (UrlTemplateImpl) obj;
+		if (key == null) {
+			if (other.key != null)
+				return false;
+		} else if (!key.equals(other.key))
+			return false;
+		return true;
+	}
+	
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java
index 9e61db4752d223c8fe041df343ce3f775b5abf02..362d25bf8c0381e107b58ed38e2ec8d87385335a 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java
@@ -19,168 +19,90 @@
  */
 package org.olat.course.nodes.livestream.ui;
 
-import static org.olat.core.gui.components.util.KeyValues.entry;
-import static org.olat.core.gui.translator.TranslatorHelper.translateAll;
-import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateInteger;
-import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateMandatory;
-
-import java.util.Arrays;
-
 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.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.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.course.nodes.livestream.LiveStreamModule;
-import org.olat.course.nodes.livestream.paella.PlayerProfile;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.core.util.resource.OresHelper;
 
 /**
  * 
- * Initial date: 5 Jun 2019<br>
+ * Initial date: 4 Jan 2021<br>
  * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  *
  */
-public class LiveStreamAdminController extends FormBasicController {
+public class LiveStreamAdminController extends BasicController {
 	
-	private static final String[] ENABLED_KEYS = new String[]{"on"};
+	private static final String SETTINGS_RES_TYPE = "Settings";
+	private static final String URL_TEMPLATES_RES_TYPE = "UrlTemplates";
 	
-	private MultipleSelectionElement enabledEl;
-	private MultipleSelectionElement multiStreamEnabledEl;
-	private TextElement urlSeparatorEl;
-	private TextElement bufferBeforeMinEl;
-	private TextElement bufferAfterMinEl;
-	private MultipleSelectionElement coachCanEditEl;
-	private SingleSelection playerProfileEl;
+	private VelocityContainer mainVC;
+	private SegmentViewComponent segmentView;
+	private Link settingsLink;
+	private Link urlTemplatesLink;
 	
-	@Autowired
-	private LiveStreamModule liveStreamModule;
+	private LiveStreamAdminSettingsController settingsCtrl;
+	private UrlTemplateListController urlTemplatesCtrl;
 
 	public LiveStreamAdminController(UserRequest ureq, WindowControl wControl) {
-		super(ureq, wControl, LAYOUT_BAREBONE);
-		initForm(ureq);
-	}
-
-	@Override
-	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator());
-		generalCont.setFormTitle(translate("admin.general.title"));
-		generalCont.setRootForm(mainForm);
-		formLayout.add("genearl", generalCont);
-		
-		enabledEl = uifactory.addCheckboxesHorizontal("admin.module.enabled", generalCont, ENABLED_KEYS,
-				translateAll(getTranslator(), ENABLED_KEYS));
-		enabledEl.select(ENABLED_KEYS[0], liveStreamModule.isEnabled());
-		
-		multiStreamEnabledEl = uifactory.addCheckboxesHorizontal("admin.multi.stream.enabled", generalCont,
-				ENABLED_KEYS, translateAll(getTranslator(), ENABLED_KEYS));
-		multiStreamEnabledEl.addActionListener(FormEvent.ONCHANGE);
-		multiStreamEnabledEl.select(ENABLED_KEYS[0], liveStreamModule.isMultiStreamEnabled());
-		
-		urlSeparatorEl = uifactory.addTextElement("admin.url.separator", 10, liveStreamModule.getUrlSeparator(), generalCont);
-		urlSeparatorEl.setMandatory(true);
-		urlSeparatorEl.setHelpTextKey("admin.url.separator.help", null);
-		
-		FormLayoutContainer defaultValuesCont = FormLayoutContainer.createDefaultFormLayout("default_values", getTranslator());
-		defaultValuesCont.setFormTitle(translate("admin.default.values.title"));
-		defaultValuesCont.setFormDescription(translate("admin.default.values.desc"));
-		defaultValuesCont.setRootForm(mainForm);
-		formLayout.add("defaultValues", defaultValuesCont);
-
-		int bufferBeforeMin = liveStreamModule.getBufferBeforeMin();
-		bufferBeforeMinEl = uifactory.addTextElement("admin.buffer.before.min", 4, String.valueOf(bufferBeforeMin),
-				defaultValuesCont);
-		bufferBeforeMinEl.setMandatory(true);
-
-		int bufferAfterMin = liveStreamModule.getBufferAfterMin();
-		bufferAfterMinEl = uifactory.addTextElement("admin.buffer.after.min", 4, String.valueOf(bufferAfterMin),
-				defaultValuesCont);
-		bufferAfterMinEl.setMandatory(true);
-		
-		coachCanEditEl = uifactory.addCheckboxesHorizontal("admin.coach.edit", defaultValuesCont, ENABLED_KEYS,
-				translateAll(getTranslator(), ENABLED_KEYS));
-		boolean coachCanEdit = liveStreamModule.isEditCoach();
-		coachCanEditEl.select(ENABLED_KEYS[0], coachCanEdit);
+		super(ureq, wControl);
+		mainVC = createVelocityContainer("admin");
 		
-		KeyValues playerProfileKV = new KeyValues();
-		for (PlayerProfile playerProfile : PlayerProfile.values()) {
-			playerProfileKV.add(entry(playerProfile.name(), translate(playerProfile.getI18nKey())));
-		}
-		playerProfileEl = uifactory.addDropdownSingleselect("admin.player.profile", defaultValuesCont, playerProfileKV.keys(),
-				playerProfileKV.values());
-		String playerProfile = liveStreamModule.getPlayerProfile();
-		if (Arrays.asList(playerProfileEl.getKeys()).contains(playerProfile)) {
-			playerProfileEl.select(playerProfile, true);
-		}
-
-		FormLayoutContainer buttonsCont = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator());
-		buttonsCont.setRootForm(mainForm);
-		formLayout.add("buttons", buttonsCont);
-		uifactory.addFormSubmitButton("save", buttonsCont);
-		
-		updateUI();
-	}
-
-	private void updateUI() {
-		boolean multiStream = multiStreamEnabledEl.isAtLeastSelected(1);
-		urlSeparatorEl.setVisible(multiStream);
-		playerProfileEl.setVisible(multiStream);
+		segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+		settingsLink = LinkFactory.createLink("admin.settings", mainVC, this);
+		segmentView.addSegment(settingsLink, true);
+		urlTemplatesLink = LinkFactory.createLink("admin.url.templates", mainVC, this);
+		segmentView.addSegment(urlTemplatesLink, false);
+
+		doOpenSettings(ureq);
+		putInitialPanel(mainVC);
 	}
 
 	@Override
-	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
-		if (source == multiStreamEnabledEl) {
-			updateUI();
+	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 == settingsLink) {
+					doOpenSettings(ureq);
+				} else if (clickedLink == urlTemplatesLink){
+					doOpenUrlTemplates(ureq);
+				}
+			}
 		}
-		super.formInnerEvent(ureq, source, event);
 	}
-
-	@Override
-	protected boolean validateFormLogic(UserRequest ureq) {
-		boolean allOk = super.validateFormLogic(ureq);
-
-		allOk &= validateMandatory(urlSeparatorEl);
-		allOk &= validateInteger(bufferBeforeMinEl, true);
-		allOk &= validateInteger(bufferAfterMinEl, true);
-
-		return allOk;
-	}
-
-	@Override
-	protected void formOK(UserRequest ureq) {
-		boolean enabled = enabledEl.isAtLeastSelected(1);
-		liveStreamModule.setEnabled(enabled);
-		
-		boolean multiStreamEnabled = multiStreamEnabledEl.isAtLeastSelected(1);
-		liveStreamModule.setMultiStreamEnabled(multiStreamEnabled);
-		
-		if (urlSeparatorEl.isVisible()) {
-			String urlSeparator = urlSeparatorEl.getValue();
-			liveStreamModule.setUrlSeparator(urlSeparator);
+	
+	private void doOpenSettings(UserRequest ureq) {
+		if (settingsCtrl != null) {
+			removeAsListenerAndDispose(settingsCtrl);
 		}
 		
-		int bufferBeforeMin = Integer.parseInt(bufferBeforeMinEl.getValue());
-		liveStreamModule.setBufferBeforeMin(bufferBeforeMin);
-		
-		int bufferAfterMin = Integer.parseInt(bufferAfterMinEl.getValue());
-		liveStreamModule.setBufferAfterMin(bufferAfterMin);
-		
-		boolean coachCanEdit = coachCanEditEl.isAtLeastSelected(1);
-		liveStreamModule.setEditCoach(coachCanEdit);
-		
-		if (playerProfileEl.isVisible()) {
-			String playerProfile = playerProfileEl.getSelectedKey();
-			liveStreamModule.setPlayerProfile(playerProfile);
-		}
+		WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(SETTINGS_RES_TYPE), null);
+		settingsCtrl = new LiveStreamAdminSettingsController(ureq, swControl);
+		listenTo(settingsCtrl);
+		mainVC.put("segmentCmp", settingsCtrl.getInitialComponent());
 	}
 	
+	private void doOpenUrlTemplates(UserRequest ureq) {
+		if (urlTemplatesCtrl != null) {
+			removeAsListenerAndDispose(urlTemplatesCtrl);
+		}
+		
+		WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(URL_TEMPLATES_RES_TYPE), null);
+		urlTemplatesCtrl = new UrlTemplateListController(ureq, swControl);
+		listenTo(urlTemplatesCtrl);
+		mainVC.put("segmentCmp", urlTemplatesCtrl.getInitialComponent());
+	}
+
 	@Override
 	protected void doDispose() {
 		//
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fcadeaf6ff60bc039c283988f05520577b67d62
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java
@@ -0,0 +1,189 @@
+/**
+ * <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.livestream.ui;
+
+import static org.olat.core.gui.components.util.KeyValues.entry;
+import static org.olat.core.gui.translator.TranslatorHelper.translateAll;
+import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateInteger;
+import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateMandatory;
+
+import java.util.Arrays;
+
+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.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.WindowControl;
+import org.olat.course.nodes.livestream.LiveStreamModule;
+import org.olat.course.nodes.livestream.paella.PlayerProfile;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 5 Jun 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class LiveStreamAdminSettingsController extends FormBasicController {
+	
+	private static final String[] ENABLED_KEYS = new String[]{"on"};
+	
+	private MultipleSelectionElement enabledEl;
+	private MultipleSelectionElement multiStreamEnabledEl;
+	private TextElement urlSeparatorEl;
+	private TextElement bufferBeforeMinEl;
+	private TextElement bufferAfterMinEl;
+	private MultipleSelectionElement coachCanEditEl;
+	private SingleSelection playerProfileEl;
+	
+	@Autowired
+	private LiveStreamModule liveStreamModule;
+
+	public LiveStreamAdminSettingsController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, LAYOUT_BAREBONE);
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator());
+		generalCont.setFormTitle(translate("admin.general.title"));
+		generalCont.setRootForm(mainForm);
+		formLayout.add("genearl", generalCont);
+		
+		enabledEl = uifactory.addCheckboxesHorizontal("admin.module.enabled", generalCont, ENABLED_KEYS,
+				translateAll(getTranslator(), ENABLED_KEYS));
+		enabledEl.select(ENABLED_KEYS[0], liveStreamModule.isEnabled());
+		
+		multiStreamEnabledEl = uifactory.addCheckboxesHorizontal("admin.multi.stream.enabled", generalCont,
+				ENABLED_KEYS, translateAll(getTranslator(), ENABLED_KEYS));
+		multiStreamEnabledEl.addActionListener(FormEvent.ONCHANGE);
+		multiStreamEnabledEl.select(ENABLED_KEYS[0], liveStreamModule.isMultiStreamEnabled());
+		
+		urlSeparatorEl = uifactory.addTextElement("admin.url.separator", 10, liveStreamModule.getUrlSeparator(), generalCont);
+		urlSeparatorEl.setMandatory(true);
+		urlSeparatorEl.setHelpTextKey("admin.url.separator.help", null);
+		
+		FormLayoutContainer defaultValuesCont = FormLayoutContainer.createDefaultFormLayout("default_values", getTranslator());
+		defaultValuesCont.setFormTitle(translate("admin.default.values.title"));
+		defaultValuesCont.setFormDescription(translate("admin.default.values.desc"));
+		defaultValuesCont.setRootForm(mainForm);
+		formLayout.add("defaultValues", defaultValuesCont);
+
+		int bufferBeforeMin = liveStreamModule.getBufferBeforeMin();
+		bufferBeforeMinEl = uifactory.addTextElement("admin.buffer.before.min", 4, String.valueOf(bufferBeforeMin),
+				defaultValuesCont);
+		bufferBeforeMinEl.setMandatory(true);
+
+		int bufferAfterMin = liveStreamModule.getBufferAfterMin();
+		bufferAfterMinEl = uifactory.addTextElement("admin.buffer.after.min", 4, String.valueOf(bufferAfterMin),
+				defaultValuesCont);
+		bufferAfterMinEl.setMandatory(true);
+		
+		coachCanEditEl = uifactory.addCheckboxesHorizontal("admin.coach.edit", defaultValuesCont, ENABLED_KEYS,
+				translateAll(getTranslator(), ENABLED_KEYS));
+		boolean coachCanEdit = liveStreamModule.isEditCoach();
+		coachCanEditEl.select(ENABLED_KEYS[0], coachCanEdit);
+		
+		KeyValues playerProfileKV = new KeyValues();
+		for (PlayerProfile playerProfile : PlayerProfile.values()) {
+			playerProfileKV.add(entry(playerProfile.name(), translate(playerProfile.getI18nKey())));
+		}
+		playerProfileEl = uifactory.addDropdownSingleselect("admin.player.profile", defaultValuesCont, playerProfileKV.keys(),
+				playerProfileKV.values());
+		String playerProfile = liveStreamModule.getPlayerProfile();
+		if (Arrays.asList(playerProfileEl.getKeys()).contains(playerProfile)) {
+			playerProfileEl.select(playerProfile, true);
+		}
+
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator());
+		buttonsCont.setRootForm(mainForm);
+		formLayout.add("buttons", buttonsCont);
+		uifactory.addFormSubmitButton("save", buttonsCont);
+		
+		updateUI();
+	}
+
+	private void updateUI() {
+		boolean multiStream = multiStreamEnabledEl.isAtLeastSelected(1);
+		urlSeparatorEl.setVisible(multiStream);
+		playerProfileEl.setVisible(multiStream);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == multiStreamEnabledEl) {
+			updateUI();
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+
+		allOk &= validateMandatory(urlSeparatorEl);
+		allOk &= validateInteger(bufferBeforeMinEl, true);
+		allOk &= validateInteger(bufferAfterMinEl, true);
+
+		return allOk;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		boolean enabled = enabledEl.isAtLeastSelected(1);
+		liveStreamModule.setEnabled(enabled);
+		
+		boolean multiStreamEnabled = multiStreamEnabledEl.isAtLeastSelected(1);
+		liveStreamModule.setMultiStreamEnabled(multiStreamEnabled);
+		
+		if (urlSeparatorEl.isVisible()) {
+			String urlSeparator = urlSeparatorEl.getValue();
+			liveStreamModule.setUrlSeparator(urlSeparator);
+		}
+		
+		int bufferBeforeMin = Integer.parseInt(bufferBeforeMinEl.getValue());
+		liveStreamModule.setBufferBeforeMin(bufferBeforeMin);
+		
+		int bufferAfterMin = Integer.parseInt(bufferAfterMinEl.getValue());
+		liveStreamModule.setBufferAfterMin(bufferAfterMin);
+		
+		boolean coachCanEdit = coachCanEditEl.isAtLeastSelected(1);
+		liveStreamModule.setEditCoach(coachCanEdit);
+		
+		if (playerProfileEl.isVisible()) {
+			String playerProfile = playerProfileEl.getSelectedKey();
+			liveStreamModule.setPlayerProfile(playerProfile);
+		}
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..10507b7ec858aebc94fc2b2d472ecf14ee2efa59
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java
@@ -0,0 +1,104 @@
+/**
+ * <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.livestream.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.course.nodes.livestream.model.UrlTemplate;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class UrlTemplateDataModel extends DefaultFlexiTableDataModel<UrlTemplate>
+implements SortableFlexiTableDataModel<UrlTemplate> {
+	
+	private final Locale locale;
+	
+	public UrlTemplateDataModel(FlexiTableColumnModel columnsModel, Locale locale) {
+		super(columnsModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey orderBy) {
+		List<UrlTemplate> rows = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort();
+		super.setObjects(rows);
+	}
+
+	@Override
+	public Object getValueAt(int row, int col) {
+		UrlTemplate reason = getObject(row);
+		return getValueAt(reason, col);
+	}
+
+	@Override
+	public Object getValueAt(UrlTemplate row, int col) {
+		switch(UrlTemplateCols.values()[col]) {
+			case id: return row.getKey();
+			case name: return row.getName();
+			case url1: return row.getUrl1();
+			case url2: return row.getUrl2();
+			default: return null;
+		}
+	}
+
+	@Override
+	public DefaultFlexiTableDataModel<UrlTemplate> createCopyWithEmptyList() {
+		return new UrlTemplateDataModel(getTableColumnModel(), locale);
+	}
+	
+	public enum UrlTemplateCols implements FlexiSortableColumnDef {
+		id("url.template.id"),
+		name("url.template.name"),
+		url1("url.template.url1"),
+		url2("url.template.url2");
+		
+		private final String i18nKey;
+		
+		private UrlTemplateCols(String i18nKey) {
+			this.i18nKey = i18nKey;
+		}
+		
+		@Override
+		public String i18nHeaderKey() {
+			return i18nKey;
+		}
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d0ce9e3bc89543601c742bd4b1074ec7baf4e9b
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java
@@ -0,0 +1,142 @@
+/**
+ * <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.livestream.ui;
+
+import java.net.URL;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+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.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.course.nodes.livestream.LiveStreamService;
+import org.olat.course.nodes.livestream.model.UrlTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class UrlTemplateEditController extends FormBasicController {
+
+	private TextElement nameEl;
+	private TextElement url1El;
+	private TextElement url2El;
+
+	private UrlTemplate urlTemplate;
+	
+	@Autowired
+	private LiveStreamService liveStreamService;
+
+	public UrlTemplateEditController(UserRequest ureq, WindowControl wControl, UrlTemplate urlTemplate) {
+		super(ureq, wControl);
+		this.urlTemplate = urlTemplate;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String name = urlTemplate != null? urlTemplate.getName(): null;
+		nameEl = uifactory.addTextElement("url.template.name", 138, name, formLayout);
+		nameEl.setMandatory(true);
+		
+		String url1 = urlTemplate != null? urlTemplate.getUrl1(): null;
+		url1El = uifactory.addTextElement("url.template.url1", 2000, url1, formLayout);
+
+		String url2 = urlTemplate != null? urlTemplate.getUrl2(): null;
+		url2El = uifactory.addTextElement("url.template.url2", 2000, url2, formLayout);
+		
+		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add("buttons", buttonLayout);
+		uifactory.addFormSubmitButton("save", buttonLayout);
+		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+	}
+
+	@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;
+		}
+		
+		url1El.clearError();
+		url2El.clearError();
+		if (!StringHelper.containsNonWhitespace(url1El.getValue()) && !StringHelper.containsNonWhitespace(url2El.getValue())) {
+			url1El.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		} else {
+			allOk &= validateUrl(url1El);
+			allOk &= validateUrl(url2El);
+		}
+		
+		return allOk;
+	}
+	
+	private boolean validateUrl(TextElement textEl) {
+		boolean allOk = true;
+		
+		if (StringHelper.containsNonWhitespace(textEl.getValue())) {
+			try {
+				new URL(textEl.getValue()).toURI();
+			} catch(Exception e) {
+				textEl.setErrorKey("error.url.not.valid", null);
+				allOk &= false;
+			}
+		}
+		
+		return allOk;
+	}
+	
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		if (urlTemplate == null) {
+			urlTemplate = liveStreamService.createUrlTemplate(nameEl.getValue());
+		}
+		
+		urlTemplate.setName(nameEl.getValue());
+		urlTemplate.setUrl1(url1El.getValue());
+		urlTemplate.setUrl2(url2El.getValue());
+		
+		urlTemplate = liveStreamService.updateUrlTemplate(urlTemplate);
+		
+		fireEvent(ureq, FormEvent.DONE_EVENT);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java
new file mode 100644
index 0000000000000000000000000000000000000000..57b7d6732e29f0e0b40a59d750d77282b3e46d25
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java
@@ -0,0 +1,199 @@
+/**
+ * <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.livestream.ui;
+
+import java.util.Comparator;
+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.FormLayoutContainer;
+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.course.nodes.livestream.LiveStreamService;
+import org.olat.course.nodes.livestream.model.UrlTemplate;
+import org.olat.course.nodes.livestream.ui.UrlTemplateDataModel.UrlTemplateCols;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class UrlTemplateListController extends FormBasicController {
+	
+	private static final String CMD_EDIT = "edit";
+	private static final String CMD_DELETE = "delete";
+
+	private static final Comparator<UrlTemplate> NAME_ASC = (t1, t2) -> t1.getName().compareToIgnoreCase(t2.getName());
+	
+	private FormLink addUrlTemplateButton;
+	private FlexiTableElement tableEl;
+	private UrlTemplateDataModel dataModel;
+	
+	private CloseableModalController cmc;
+	private UrlTemplateEditController editUrlTemplateCtrl;
+	private DialogBoxController deleteDialogCtrl;
+	
+	@Autowired
+	private LiveStreamService liveStreamService;
+
+	public UrlTemplateListController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, LAYOUT_BAREBONE);
+		initForm(ureq);
+		loadModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FormLayoutContainer buttonsTopCont = FormLayoutContainer.createButtonLayout("buttons.top", getTranslator());
+		buttonsTopCont.setElementCssClass("o_button_group o_button_group_right");
+		buttonsTopCont.setRootForm(mainForm);
+		formLayout.add(buttonsTopCont);
+		
+		addUrlTemplateButton = uifactory.addFormLink("url.template.add", buttonsTopCont, Link.BUTTON);
+		addUrlTemplateButton.setIconLeftCSS("o_icon o_icon-lg o_icon_add");
+		
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, UrlTemplateCols.id));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UrlTemplateCols.name));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UrlTemplateCols.url1));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UrlTemplateCols.url2));
+		DefaultFlexiColumnModel editColumn = new DefaultFlexiColumnModel("url.template.edit", -1, CMD_EDIT,
+				new StaticFlexiCellRenderer(translate("url.template.edit"), CMD_EDIT, "", "", null));
+		editColumn.setExportable(false);
+		columnsModel.addFlexiColumnModel(editColumn);
+		DefaultFlexiColumnModel deleteColumn = new DefaultFlexiColumnModel("url.template.delete", -1, CMD_DELETE,
+				new StaticFlexiCellRenderer(translate("url.template.delete"), CMD_DELETE, "", "", null));
+		deleteColumn.setExportable(false);
+		columnsModel.addFlexiColumnModel(deleteColumn);
+		
+		dataModel = new UrlTemplateDataModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "table", dataModel, 20, false, getTranslator(), formLayout);
+		tableEl.setExportEnabled(true);
+		tableEl.setAndLoadPersistedPreferences(ureq, "livestream-url-templates");
+	}
+
+	private void loadModel() {
+		List<UrlTemplate> urlTemplates = liveStreamService.getAllUrlTemplates();
+		urlTemplates.sort(NAME_ASC);
+		dataModel.setObjects(urlTemplates);
+		tableEl.reset(false, false, true);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == addUrlTemplateButton) {
+			doAddUrlTemplate(ureq);
+		} else if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				String cmd = se.getCommand();
+				UrlTemplate urlTemplate = dataModel.getObject(se.getIndex());
+				if (CMD_EDIT.equals(cmd)) {
+					doEditUrlTemplate(ureq, urlTemplate);
+				} else if (CMD_DELETE.equals(cmd)) {
+					doConfirmDeletUrlTemplate(ureq, urlTemplate);
+				}
+			}
+		} 
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(editUrlTemplateCtrl == source) {
+			if(event == Event.DONE_EVENT) {
+				loadModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		} else if (deleteDialogCtrl == source) {
+			if (DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
+				UrlTemplate urlTemplate = (UrlTemplate)deleteDialogCtrl.getUserObject();
+				liveStreamService.deleteUrlTemplate(urlTemplate);
+				loadModel();
+			}
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(editUrlTemplateCtrl);
+		removeAsListenerAndDispose(cmc);
+		cmc = null;
+	}
+
+	private void doAddUrlTemplate(UserRequest ureq) {
+		editUrlTemplateCtrl = new UrlTemplateEditController(ureq, getWindowControl(), null);
+		listenTo(editUrlTemplateCtrl);
+		
+		String title = translate("url.template.add");
+		cmc = new CloseableModalController(getWindowControl(), "close", editUrlTemplateCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+
+	private void doEditUrlTemplate(UserRequest ureq, UrlTemplate urlTemplate) {
+		editUrlTemplateCtrl = new UrlTemplateEditController(ureq, getWindowControl(), urlTemplate);
+		listenTo(editUrlTemplateCtrl);
+		
+		String title = translate("url.template.edit.title");
+		cmc = new CloseableModalController(getWindowControl(), "close", editUrlTemplateCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doConfirmDeletUrlTemplate(UserRequest ureq, UrlTemplate urlTemplate) {
+		String text = translate("url.template.delete.confirm", new String[] { urlTemplate.getName() });
+		deleteDialogCtrl = activateYesNoDialog(ureq, translate("url.template.delete"), text, deleteDialogCtrl);
+		deleteDialogCtrl.setUserObject(urlTemplate);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html
new file mode 100644
index 0000000000000000000000000000000000000000..2dd819318ee3a19450cc06a3238dc4ec6ad3a360
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html
@@ -0,0 +1,9 @@
+<div class="clearfix">
+	#if($r.visible("segments"))
+		$r.render("segments")<br/>
+	#end
+		
+	#if($r.available("segmentCmp"))
+		$r.render("segmentCmp")
+	#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties
index 142f7deee22c6b171f38146f5d7f72550691bb08..554802e8c5f678fdf570eb825cfac607e9680311 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties
@@ -9,12 +9,15 @@ admin.menu.title.alt=$:\admin.menu.title
 admin.module.enabled=Kursbaustein
 admin.multi.stream.enabled=Multistream
 admin.player.profile=Stream
+admin.settings=Einstellungen
 admin.url.separator=URL Trennzeichen
 admin.url.separator.help=Trennzeichen, um mehrere URLs eines einzelnen Termins zu trennen.
+admin.url.templates=URL Vorlagen
 config.buffer.before.min=Vorlaufzeit (in Minuten)
 config.buffer.after.min=Nachlaufzeit (in Minuten)
 config.coach.edit=Betreuer darf Termine bearbeiten
 config.player.profile=$\:admin.player.profile
+error.url.not.valid=URL nicht g\u00FCltig
 form.error.wrong.int=Falsches Zahlenformat. Beispiele\: 2, 10, 144
 link.text=Video Livestream
 list.title=Anstehende Livestreams
@@ -39,6 +42,15 @@ table.header.end=$org.olat.commons.calendar\:cal.form.end
 table.header.location=$org.olat.commons.calendar\:cal.form.location
 table.header.subject=$org.olat.commons.calendar\:cal.form.subject
 table.header.viewers=Zuschauer
+url.template.add=URL Vorlage erstellen
+url.template.delete=L\u00F6schen
+url.template.delete.confirm=Wollen die die URL Vorlage "{0}" wirklich l\u00F6schen?
+url.template.edit=Bearbeiten
+url.template.edit.title=URL Vorlage bearbeiten
+url.template.id=ID
+url.template.name=Name
+url.template.url1=URL 1
+url.template.url2=URL 2
 viewer.error.browser=Der Livestream kann in diesem Browser nicht angezeigt werden. Bitte verwenden Sie einen anderen Browser.
 viewer.error.stream=Der Livestream kann nicht angezeigt werden. Vermutlich wird der Livestream noch nicht ausgestrahlt.
 viewer.no.stream=Aktuell wird kein Livestream ausgestrahlt.
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties
index 23424e7bfe29c9ead756a657b9309b8cb878c72e..3a884578f548e49cb1511c60e52147bc7c65b09a 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties
@@ -9,13 +9,16 @@ admin.menu.title.alt=$:\admin.menu.title
 admin.module.enabled=Course element
 admin.multi.stream.enabled=Multi stream
 admin.player.profile=Stream
+admin.settings=Settings
 admin.url.separator=URL separator
 admin.url.separator.help=Char / String to separate multiple URLs of a single event.
+admin.url.templates=URL templates
 condition.accessibility.title=Access
 config.buffer.before.min=Buffer time before start (in minutes)
 config.buffer.after.min=Buffer time after end (in minutes)
 config.coach.edit=Coach is allowed to edit
 config.player.profile=$\:admin.player.profile
+error.url.not.valid=URL not valid
 form.error.wrong.int=Wrong numeral format. Examples\: 2, 10, 144
 link.text=Video live stream
 list.title=Upcoming live streams
@@ -40,6 +43,15 @@ table.header.end=$org.olat.commons.calendar\:cal.form.end
 table.header.location=$org.olat.commons.calendar\:cal.form.location
 table.header.subject=$org.olat.commons.calendar\:cal.form.subject
 table.header.viewers=Viewers
+url.template.add=Add URL template
+url.template.delete=Delete
+url.template.delete.confirm=Do your really want to delete the URL template "{0}"?
+url.template.edit=Edit
+url.template.edit.title=Edit URL template
+url.template.id=ID
+url.template.name=Name
+url.template.url1=URL 1
+url.template.url2=URL 2
 viewer.error.browser=The livestream cannot be displayed in this browser. Please use a different browser.
 viewer.error.stream=It is not possible to show the live stream. Probably is the live stream not broadcasted yet.
 viewer.no.stream=Currently no live stream is broadcasted.
diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java
index fd94af7dd80fcf528aa40ea40ffbd5d75eb26d43..5cf53e68740089a78b0ac27db2d81ef4468e2cab 100644
--- a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java
+++ b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java
@@ -42,7 +42,7 @@ import org.olat.core.logging.Tracing;
 import org.olat.core.logging.activity.LoggingObject;
 import org.olat.core.util.prefs.Preferences;
 import org.olat.core.util.prefs.db.DbStorage;
-import org.olat.course.nodes.livestream.manager.LiveStreamLaunchDAO;
+import org.olat.course.nodes.livestream.manager.LaunchDAO;
 import org.olat.modules.quality.QualityDataCollection;
 import org.olat.modules.quality.QualityService;
 import org.olat.properties.Property;
@@ -82,7 +82,7 @@ public class OLATUpgrade_14_2_0 extends OLATUpgrade {
 	@Autowired
 	private RepositoryEntryDAO repositoryEntryDao;
 	@Autowired
-	private LiveStreamLaunchDAO liveStreamLaunchDao;
+	private LaunchDAO liveStreamLaunchDao;
 	@Autowired
 	private BaseSecurity securityManager;
 	
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index 48a909947f82c2dfc491d97e6f03f765b1249eeb..999de0ab661422c7b6144116bf8ec0cbe9620fb1 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -112,6 +112,7 @@
 		<class>org.olat.course.nodes.gta.model.TaskRevisionImpl</class>
 		<class>org.olat.course.nodes.gta.model.TaskRevisionDateImpl</class>
 		<class>org.olat.course.nodes.livestream.model.LaunchImpl</class>
+		<class>org.olat.course.nodes.livestream.model.UrlTemplateImpl</class>
 		<class>org.olat.course.certificate.model.CertificateImpl</class>
 		<class>org.olat.course.certificate.model.CertificateStandalone</class>
 		<class>org.olat.course.certificate.model.CertificateLightImpl</class>
diff --git a/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql b/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql
index 9587559c727e9cdaf0bbdbfb7743115d4f850ccd..b2eba18f86a483b3beac4dedb52db33bb09090d1 100644
--- a/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql
+++ b/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql
@@ -3,3 +3,13 @@ alter table o_bbb_meeting add column b_password varchar(64) default null;
 alter table o_bbb_meeting add column b_directory varchar(64) default null;
 alter table o_bbb_meeting add constraint bbb_dir_idx unique (b_directory);
 
+-- Livestream
+create table o_livestream_url_template (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   l_name varchar2(64) not null,
+   l_url1 varchar2(2048),
+   l_url2 varchar2(2048),
+   primary key (id)
+);
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 3761aa106cf17cb68cfc106ba8858adc441d020b..001510b7d354558bc205bb6226140b468f6bc9e8 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -3044,6 +3044,16 @@ create table o_livestream_launch (
    fk_identity bigint not null,
    primary key (id)
 );
+-- Livestream
+create table o_livestream_url_template (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   l_name varchar2(64) not null,
+   l_url1 varchar2(2048),
+   l_url2 varchar2(2048),
+   primary key (id)
+);
 
 -- grading
 create table o_grad_to_identity (
diff --git a/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql b/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql
index 65ea60c3d100da045d0902317ef3182dbea8db13..8bc9812d3be2f5a7e24fd9a767d9d8a997040ceb 100644
--- a/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql
+++ b/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql
@@ -3,3 +3,13 @@ alter table o_bbb_meeting add b_password varchar2(64) default null;
 alter table o_bbb_meeting add b_directory varchar2(64) default null;
 alter table o_bbb_meeting add constraint bbb_dir_idx unique (b_directory);
 
+-- Livestream
+create table o_livestream_url_template (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   l_name varchar2(64) not null,
+   l_url1 varchar2(2048),
+   l_url2 varchar2(2048),
+   primary key (id)
+);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 8b52bea73b300b66e48604a1a44633f60edf2e3c..fd1c199bcbff3fe22c1cefe0fe730c91052006ab 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -3124,6 +3124,15 @@ create table o_livestream_launch (
    fk_identity  number(20) not null,
    primary key (id)
 );
+create table o_livestream_url_template (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   l_name varchar2(64) not null,
+   l_url1 varchar2(2048),
+   l_url2 varchar2(2048),
+   primary key (id)
+);
 
 -- grading
 create table o_grad_to_identity (
diff --git a/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql b/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql
index 9587559c727e9cdaf0bbdbfb7743115d4f850ccd..282bb14f8a5950e127e73715a7ba7b53b92bb4c1 100644
--- a/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql
+++ b/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql
@@ -3,3 +3,13 @@ alter table o_bbb_meeting add column b_password varchar(64) default null;
 alter table o_bbb_meeting add column b_directory varchar(64) default null;
 alter table o_bbb_meeting add constraint bbb_dir_idx unique (b_directory);
 
+-- Livestream
+create table o_livestream_url_template (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   l_name varchar(64) not null,
+   l_url1 varchar(2048),
+   l_url2 varchar(2048),
+   primary key (id)
+);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 5895c609db930f377c2322065fa6b11cc30b7c1a..6fb5eb0d84569d55720609e383d7a56193793767 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -3067,6 +3067,16 @@ create table o_livestream_launch (
    primary key (id)
 );
 
+create table o_livestream_url_template (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   l_name varchar(64) not null,
+   l_url1 varchar(2048),
+   l_url2 varchar(2048),
+   primary key (id)
+);
+
 -- grading
 create table o_grad_to_identity (
    id bigserial,
diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/LaunchDAOTest.java
similarity index 97%
rename from src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java
rename to src/test/java/org/olat/course/nodes/livestream/manager/LaunchDAOTest.java
index 24aa977885a178b8431e6c024713503e5f65e57e..e71eabb7357ef471e0bc908152b14aa24f31f024 100644
--- a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java
+++ b/src/test/java/org/olat/course/nodes/livestream/manager/LaunchDAOTest.java
@@ -42,10 +42,10 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  *
  */
-public class LiveStreamLaunchDAOTest extends OlatTestCase {
+public class LaunchDAOTest extends OlatTestCase {
 	
 	@Autowired
-	private LiveStreamLaunchDAO sut;
+	private LaunchDAO sut;
 	@Autowired
 	private DB dbInstance;
 	
diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b806d8a37424eb430131aa3a714c03261c4ec961
--- /dev/null
+++ b/src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java
@@ -0,0 +1,119 @@
+/**
+ * <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.livestream.manager;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.olat.test.JunitTestHelper.random;
+
+import java.util.List;
+
+import org.assertj.core.api.SoftAssertions;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.course.nodes.livestream.model.UrlTemplate;
+import org.olat.course.nodes.livestream.model.UrlTemplateImpl;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 4 Jan 2021<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class UrlTemplateDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	@Autowired
+	private UrlTemplateDAO sut;
+
+	@Test
+	public void shouldCreateUrlTemplate() {
+		String name = random();
+		
+		UrlTemplate urlTemplate = sut.create(name);
+		dbInstance.commitAndCloseSession();
+		
+		SoftAssertions softly = new SoftAssertions();
+		softly.assertThat(urlTemplate.getKey()).isNotNull();
+		softly.assertThat(((UrlTemplateImpl) urlTemplate).getCreationDate()).isNotNull();
+		softly.assertThat(((UrlTemplateImpl) urlTemplate).getLastModified()).isNotNull();
+		softly.assertThat(urlTemplate.getName()).isEqualTo(name);
+		softly.assertAll();
+	}
+
+	@Test
+	public void shouldUpdateUrlTemplate() {
+		UrlTemplate urlTemplate = sut.create(random());
+		dbInstance.commitAndCloseSession();
+		
+		String name = random();
+		urlTemplate.setName(name);
+		String url1 = random();
+		urlTemplate.setUrl1(url1);
+		String url2 = random();
+		urlTemplate.setUrl2(url2);
+		urlTemplate = sut.update(urlTemplate);
+		
+		UrlTemplate reloaded = sut.loadByKey(urlTemplate.getKey());
+		SoftAssertions softly = new SoftAssertions();
+		softly.assertThat(reloaded.getName()).isEqualTo(name);
+		softly.assertThat(reloaded.getUrl1()).isEqualTo(url1);
+		softly.assertThat(reloaded.getUrl2()).isEqualTo(url2);
+		softly.assertAll();
+	}
+	
+	@Test
+	public void shouldLoadAll() {
+		sut.create(random());
+		dbInstance.commitAndCloseSession();
+		
+		List<UrlTemplate> all = sut.loadAll();
+		
+		assertThat(all).hasSizeGreaterThan(0);
+	}
+	
+	@Test
+	public void shouldLoadTemplateUrlByKey() {
+		UrlTemplate urlTemplate = sut.create(random());
+		dbInstance.commitAndCloseSession();
+		
+		UrlTemplate reloaded = sut.loadByKey(urlTemplate.getKey());
+		
+		assertThat(reloaded).isEqualTo(urlTemplate);
+	}
+	
+	@Test
+	public void shouldDelete() {
+		UrlTemplate urlTemplate = sut.create(random());
+		dbInstance.commitAndCloseSession();
+		
+		sut.delete(urlTemplate);
+		
+		UrlTemplate reloaded = sut.loadByKey(urlTemplate.getKey());
+		assertThat(reloaded).isNull();
+	}
+
+
+
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index c060df03a33aa0af539f0d8203018a080892298a..4ad19ccf9542a8fbae2d0c5483e5fc238df1678f 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -185,7 +185,8 @@ import org.junit.runners.Suite;
 	org.olat.course.nodes.gta.manager.GTATaskRevisionDAOTest.class,
 	org.olat.course.nodes.gta.manager.GTAIdentityMarkDAOTest.class,
 	org.olat.course.nodes.gta.rule.GTAReminderRuleTest.class,
-	org.olat.course.nodes.livestream.manager.LiveStreamLaunchDAOTest.class,
+	org.olat.course.nodes.livestream.manager.LaunchDAOTest.class,
+	org.olat.course.nodes.livestream.manager.UrlTemplateDAOTest.class,
 	org.olat.course.nodes.members.manager.MembersManagerTest.class,
 	org.olat.course.nodes.pf.manager.PFManagerTest.class,
 	org.olat.course.assessment.AssessmentManagerTest.class,