diff --git a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java index 9c8f3f4295bd29ce8d08a1e0afcba84f74459c8d..af89aaf7eddc82a6f5165107b22dcf4d2195853a 100644 --- a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java +++ b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java @@ -65,6 +65,7 @@ public class LiveStreamCourseNode extends AbstractAccessableCourseNode { public static final String CONFIG_BUFFER_BEFORE_MIN = "bufferBeforeMin"; public static final String CONFIG_BUFFER_AFTER_MIN = "bufferBeforeAfter"; public static final String CONFIG_COACH_CAN_EDIT = "coachCanEdit"; + public static final String CONFIG_PLAYER_PROFILE = "playerProfile"; public LiveStreamCourseNode() { super(TYPE); @@ -130,6 +131,8 @@ public class LiveStreamCourseNode extends AbstractAccessableCourseNode { config.setIntValue(CONFIG_BUFFER_BEFORE_MIN, liveStreamModule.getBufferBeforeMin()); config.setIntValue(CONFIG_BUFFER_AFTER_MIN, liveStreamModule.getBufferAfterMin()); config.setBooleanEntry(CONFIG_COACH_CAN_EDIT, liveStreamModule.isEditCoach()); + // CONFIG_PLAYER_PROFILE has no default value, because previously the multi + // stream option has to be enabled and the default value has to be selected. } config.setConfigurationVersion(CURRENT_VERSION); } diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamModule.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamModule.java index cf91567e79608746dab92000dd8acb9ee1d4d1e6..2d4c701c7abca5dbbe8afdd3ceaa82c5346ad804 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamModule.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamModule.java @@ -37,13 +37,17 @@ import org.springframework.stereotype.Service; public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOff { public static final String LIVE_STREAM_ENABLED = "live.stream.enabled"; + public static final String LIVE_STREAM_MULTI_STREAM_ENABLED = "live.stream.multi.stream.enabled"; public static final String LIVE_STREAM_URL_SEPARATOR = "live.stream.url.separator"; public static final String LIVE_STREAM_BUFFER_BEFORE_MIN = "live.stream.buffer.before.min"; public static final String LIVE_STREAM_BUFFER_AFTER_MIN = "live.stream.buffer.after.min"; public static final String LIVE_STREAM_EDIT_COACH = "live.stream.edit.coach"; + public static final String LIVE_STREAM_PLAYER_PROFILE = "live.stream.player.profile"; @Value("${live.stream.enabled:false}") private boolean enabled; + @Value("${live.stream.multi.stream.enabled:false}") + private boolean multiStreamEnabled; @Value("${live.stream.url.separator:,}") private String urlSeparator; @Value("${live.stream.buffer.before.min:5}") @@ -52,6 +56,8 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf private int bufferAfterMin; @Value("${live.stream.edit.coach:false}") private boolean editCoach; + @Value("${live.stream.player.profile:both}") + private String playerProfile; @Autowired public LiveStreamModule(CoordinatorManager coordinatorManager) { @@ -65,6 +71,11 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf enabled = "true".equals(enabledObj); } + String multiStreamEnabledObj = getStringPropertyValue(LIVE_STREAM_MULTI_STREAM_ENABLED, true); + if(StringHelper.containsNonWhitespace(multiStreamEnabledObj)) { + multiStreamEnabled = "true".equals(multiStreamEnabledObj); + } + String urlSeparatorObj = getStringPropertyValue(LIVE_STREAM_URL_SEPARATOR, true); if(StringHelper.containsNonWhitespace(urlSeparatorObj)) { urlSeparator = urlSeparatorObj; @@ -84,6 +95,11 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf if(StringHelper.containsNonWhitespace(editCoachObj)) { editCoach = "true".equals(editCoachObj); } + + String playerProfileObj = getStringPropertyValue(LIVE_STREAM_PLAYER_PROFILE, true); + if(StringHelper.containsNonWhitespace(playerProfileObj)) { + playerProfile = playerProfileObj; + } } @Override @@ -101,6 +117,15 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf setStringProperty(LIVE_STREAM_ENABLED, Boolean.toString(enabled), true); } + public boolean isMultiStreamEnabled() { + return multiStreamEnabled; + } + + public void setMultiStreamEnabled(boolean multiStreamEnabled) { + this.multiStreamEnabled = multiStreamEnabled; + setStringProperty(LIVE_STREAM_MULTI_STREAM_ENABLED, Boolean.toString(multiStreamEnabled), true); + } + public String getUrlSeparator() { return urlSeparator; } @@ -137,4 +162,13 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf setStringProperty(LIVE_STREAM_EDIT_COACH, Boolean.toString(editCoach), true); } + public String getPlayerProfile() { + return playerProfile; + } + + public void setPlayerProfile(String playerProfile) { + this.playerProfile = playerProfile; + setStringProperty(LIVE_STREAM_PLAYER_PROFILE, playerProfile, true); + } + } 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 new file mode 100644 index 0000000000000000000000000000000000000000..5b7a52b92f5d4a1ea2b5aab3f4867366e4557bca --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java @@ -0,0 +1,150 @@ +/** + * <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.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarUtils; +import org.olat.commons.calendar.model.KalendarEvent; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.course.nodes.cal.CourseCalendars; +import org.olat.course.nodes.livestream.LiveStreamEvent; +import org.olat.course.nodes.livestream.LiveStreamService; +import org.olat.course.nodes.livestream.model.LiveStreamEventImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.concurrent.CustomizableThreadFactory; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 28 May 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class LiveStreamServiceImpl implements LiveStreamService { + + private ScheduledExecutorService scheduler; + + @Autowired + private CalendarManager calendarManager; + + @Override + public ScheduledExecutorService getScheduler() { + if (scheduler == null) { + ThreadFactory threadFactory = new CustomizableThreadFactory("oo-livestream-"); + scheduler = Executors.newScheduledThreadPool(1, threadFactory); + } + return scheduler; + } + + @Override + public List<? extends LiveStreamEvent> getRunningEvents(CourseCalendars calendars, int bufferBeforeMin, + int bufferAfterMin) { + Date now = new Date(); + + Calendar cFrom = Calendar.getInstance(); + cFrom.setTime(now); + cFrom.add(Calendar.MINUTE, -bufferAfterMin); + Date from = cFrom.getTime(); + + Calendar cTo = Calendar.getInstance(); + cTo.setTime(now); + cTo.add(Calendar.MINUTE, bufferBeforeMin); + Date to = cTo.getTime(); + + return getLiveStreamEvents(calendars, from, to); + } + + @Override + public List<? extends LiveStreamEvent> getUpcomingEvents(CourseCalendars calendars, int bufferBeforeMin) { + Date now = new Date(); + Calendar cFrom = Calendar.getInstance(); + cFrom.setTime(now); + cFrom.add(Calendar.MINUTE, bufferBeforeMin); + Date from = cFrom.getTime(); + Calendar cTo = Calendar.getInstance(); + cTo.setTime(now); + cTo.add(Calendar.YEAR, 10); + Date to = cTo.getTime(); + + return getLiveStreamEvents(calendars, from, to).stream() + .filter(notStartedFilter(from)) + .collect(Collectors.toList()); + } + + private Predicate<LiveStreamEvent> notStartedFilter(Date from) { + return (LiveStreamEvent e) -> { + return !e.getBegin().before(from); + }; + } + + private List<? extends LiveStreamEvent> getLiveStreamEvents(CourseCalendars calendars, Date from, Date to) { + List<LiveStreamEvent> liveStreamEvents = new ArrayList<>(); + for (KalendarRenderWrapper cal : calendars.getCalendars()) { + if(cal != null) { + boolean privateEventsVisible = cal.isPrivateEventsVisible(); + List<KalendarEvent> events = calendarManager.getEvents(cal.getKalendar(), from, to, privateEventsVisible); + for(KalendarEvent event:events) { + if(!privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_PRIVATE) { + continue; + } + + if (isLiveStream(event)) { + boolean timeOnly = !privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_X_FREEBUSY; + LiveStreamEventImpl liveStreamEvent = toLiveStreamEvent(event, timeOnly); + liveStreamEvents.add(liveStreamEvent); + }; + } + } + } + + return liveStreamEvents; + } + + private boolean isLiveStream(KalendarEvent event) { + return event.getLiveStreamUrl() != null; + } + + private LiveStreamEventImpl toLiveStreamEvent(KalendarEvent event, boolean timeOnly) { + LiveStreamEventImpl liveStreamEvent = new LiveStreamEventImpl(); + liveStreamEvent.setId(event.getID()); + liveStreamEvent.setAllDayEvent(event.isAllDayEvent()); + liveStreamEvent.setBegin(event.getBegin()); + Date end = CalendarUtils.endOf(event); + liveStreamEvent.setEnd(end); + liveStreamEvent.setLiveStreamUrl(event.getLiveStreamUrl()); + if (!timeOnly) { + liveStreamEvent.setSubject(event.getSubject()); + liveStreamEvent.setDescription(event.getDescription()); + liveStreamEvent.setLocation(event.getLocation()); + } + return liveStreamEvent; + } +} diff --git a/src/main/java/org/olat/course/nodes/livestream/paella/PaellaMapper.java b/src/main/java/org/olat/course/nodes/livestream/paella/PaellaMapper.java index 2593b785fa199c2e83b6a1a63cd2c8a02f3f091b..ba11b49eca69f2b758d713b938835aeb9f8d4822 100644 --- a/src/main/java/org/olat/course/nodes/livestream/paella/PaellaMapper.java +++ b/src/main/java/org/olat/course/nodes/livestream/paella/PaellaMapper.java @@ -49,9 +49,11 @@ public class PaellaMapper implements Mapper { private final ObjectMapper mapper = new ObjectMapper(); private final Streams streams; + private final PlayerProfile playerProfile; - public PaellaMapper(Streams streams) { + public PaellaMapper(Streams streams, PlayerProfile playerProfile) { this.streams = streams; + this.playerProfile = playerProfile; } @Override @@ -210,18 +212,9 @@ public class PaellaMapper implements Mapper { sb.append(" 'es.upv.paella.translecture.captionsPlugin': {'enabled':true},"); sb.append(" 'es.upv.paella.trimmingPlayerPlugin': {'enabled':true},"); sb.append(" 'es.upv.paella.windowTitlePlugin': {'enabled': true},"); - sb.append(" 'es.upv.paella.singleStreamProfilePlugin': {"); - sb.append(" 'enabled': true,"); - sb.append(" 'videoSets': ["); - sb.append(" { 'icon':'professor_icon.svg', 'id':'professor', 'content':['stream1']},"); - sb.append(" { 'icon':'slide_icon.svg', 'id':'slide', 'content':['stream2']}"); - sb.append(" ]"); - sb.append(" },"); - sb.append(" 'es.upv.paella.dualStreamProfilePlugin': { 'enabled':true,"); - sb.append(" 'videoSets': ["); - sb.append(" { 'icon':'slide_professor_icon.svg', 'id':'slide_over_professor', 'content':['stream1','stream2'] }"); - sb.append(" ]"); - sb.append(" }"); + playerProfile.appendPlayerConfig(sb); + // The last plugin must not have a comma at the and of the configs. + sb.append(" 'es.upv.paella.windowTitlePlugin': {'enabled': true}"); sb.append(" }"); sb.append(" },"); sb.append(" 'defaultProfile':'presenter_presentation',"); diff --git a/src/main/java/org/olat/course/nodes/livestream/paella/PlayerProfile.java b/src/main/java/org/olat/course/nodes/livestream/paella/PlayerProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..707fed01afbbb6b58474079943e2459d5ea4e62c --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/paella/PlayerProfile.java @@ -0,0 +1,84 @@ +/** + * <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.paella; + +import org.olat.core.gui.render.StringOutput; + +/** + * + * Initial date: 17 Dec 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public enum PlayerProfile { + + both("player.profile.both") { + @Override + public void appendPlayerConfig(StringOutput sb) { + sb.append(" 'es.upv.paella.singleStreamProfilePlugin': {"); + sb.append(" 'enabled': true,"); + sb.append(" 'videoSets': ["); + sb.append(" { 'icon':'professor_icon.svg', 'id':'professor', 'content':['stream1']},"); + sb.append(" { 'icon':'slide_icon.svg', 'id':'slide', 'content':['stream2']}"); + sb.append(" ]"); + sb.append(" },"); + sb.append(" 'es.upv.paella.dualStreamProfilePlugin': { 'enabled':true,"); + sb.append(" 'videoSets': ["); + sb.append(" { 'icon':'slide_professor_icon.svg', 'id':'slide_over_professor', 'content':['stream1','stream2'] }"); + sb.append(" ]"); + sb.append(" },"); + } + }, + stream1("player.profile.stream1") { + @Override + public void appendPlayerConfig(StringOutput sb) { + sb.append(" 'es.upv.paella.singleStreamProfilePlugin': {"); + sb.append(" 'enabled': true,"); + sb.append(" 'videoSets': ["); + sb.append(" { 'icon':'professor_icon.svg', 'id':'professor', 'content':['stream1']}"); + sb.append(" ]"); + sb.append(" },"); + } + }, + stream2("player.profile.stream2") { + @Override + public void appendPlayerConfig(StringOutput sb) { + sb.append(" 'es.upv.paella.singleStreamProfilePlugin': {"); + sb.append(" 'enabled': true,"); + sb.append(" 'videoSets': ["); + sb.append(" { 'icon':'slide_icon.svg', 'id':'slide', 'content':['stream2']}"); + sb.append(" ]"); + sb.append(" },"); + } + }; + + private final String i18nKey; + + private PlayerProfile(String i18nKey) { + this.i18nKey = i18nKey; + } + + public String getI18nKey() { + return i18nKey; + } + + public abstract void appendPlayerConfig(StringOutput sb); + +} 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 3542060814b45dbdc6102fd00b2a16b7c0bb00c7..906d7470b2281257f09f3a745edb0820fd3dfdcc 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,21 +19,29 @@ */ 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 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; +import edu.emory.mathcs.backport.java.util.Arrays; + /** * * Initial date: 5 Jun 2019<br> @@ -45,17 +53,18 @@ public class LiveStreamAdminController 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 LiveStreamAdminController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl, LAYOUT_BAREBONE); - initForm(ureq); } @@ -70,6 +79,11 @@ public class LiveStreamAdminController extends FormBasicController { 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); @@ -95,10 +109,37 @@ public class LiveStreamAdminController extends FormBasicController { 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 @@ -117,8 +158,13 @@ public class LiveStreamAdminController extends FormBasicController { boolean enabled = enabledEl.isAtLeastSelected(1); liveStreamModule.setEnabled(enabled); - String urlSeparator = urlSeparatorEl.getValue(); - liveStreamModule.setUrlSeparator(urlSeparator); + 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); @@ -128,6 +174,11 @@ public class LiveStreamAdminController extends FormBasicController { boolean coachCanEdit = coachCanEditEl.isAtLeastSelected(1); liveStreamModule.setEditCoach(coachCanEdit); + + if (playerProfileEl.isVisible()) { + String playerProfile = playerProfileEl.getSelectedKey(); + liveStreamModule.setPlayerProfile(playerProfile); + } } @Override diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamConfigController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamConfigController.java index 0c417d030d96809987b9a8ef8d2d0e758cc69015..638740ebdd5876d0e7957ff1403443cef9e22366 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamConfigController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamConfigController.java @@ -19,19 +19,27 @@ */ 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 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.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.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.course.nodes.LiveStreamCourseNode; +import org.olat.course.nodes.livestream.LiveStreamModule; +import org.olat.course.nodes.livestream.paella.PlayerProfile; import org.olat.modules.ModuleConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +import edu.emory.mathcs.backport.java.util.Arrays; /** * @@ -48,6 +56,10 @@ public class LiveStreamConfigController extends FormBasicController { private TextElement bufferBeforeMinEl; private TextElement bufferAfterMinEl; private MultipleSelectionElement coachCanEditEl; + private SingleSelection playerProfileEl; + + @Autowired + private LiveStreamModule liveStreamModule; public LiveStreamConfigController(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfiguration) { super(ureq, wControl); @@ -73,6 +85,20 @@ public class LiveStreamConfigController extends FormBasicController { boolean coachCanEdit = config.getBooleanSafe(LiveStreamCourseNode.CONFIG_COACH_CAN_EDIT); coachCanEditEl.select(ENABLED_KEYS[0], coachCanEdit); + if (liveStreamModule.isMultiStreamEnabled()) { + KeyValues playerProfileKV = new KeyValues(); + for (PlayerProfile playerProfile : PlayerProfile.values()) { + playerProfileKV.add(entry(playerProfile.name(), translate(playerProfile.getI18nKey()))); + } + playerProfileEl = uifactory.addDropdownSingleselect("config.player.profile", formLayout, playerProfileKV.keys(), + playerProfileKV.values()); + String playerProfile = config.getStringValue(LiveStreamCourseNode.CONFIG_PLAYER_PROFILE, + liveStreamModule.getPlayerProfile()); + if (Arrays.asList(playerProfileEl.getKeys()).contains(playerProfile)) { + playerProfileEl.select(playerProfile, true); + } + } + uifactory.addFormSubmitButton("save", formLayout); } @@ -101,6 +127,11 @@ public class LiveStreamConfigController extends FormBasicController { boolean coachCanEdit = coachCanEditEl.isAtLeastSelected(1); config.setBooleanEntry(LiveStreamCourseNode.CONFIG_COACH_CAN_EDIT, coachCanEdit); + if (playerProfileEl != null) { + String playerProfile = playerProfileEl.getSelectedKey(); + config.setStringValue(LiveStreamCourseNode.CONFIG_PLAYER_PROFILE, playerProfile); + } + return config; } diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamVideoController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamVideoController.java index 9ea0d462e783d06b0c9b9b24cac7d4f5625afaf1..04743bf6211c1dfc645c95a72f7b4b6c892382e2 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamVideoController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamVideoController.java @@ -36,6 +36,7 @@ import org.olat.core.util.UserSession; import org.olat.course.nodes.livestream.LiveStreamEvent; import org.olat.course.nodes.livestream.paella.PaellaFactory; import org.olat.course.nodes.livestream.paella.PaellaMapper; +import org.olat.course.nodes.livestream.paella.PlayerProfile; import org.olat.course.nodes.livestream.paella.Streams; import org.springframework.beans.factory.annotation.Autowired; @@ -49,14 +50,16 @@ public class LiveStreamVideoController extends BasicController { private final VelocityContainer mainVC; + private final PlayerProfile playerProfile; private final List<MapperKey> mappers = new ArrayList<>(); private String url; @Autowired private MapperService mapperService; - protected LiveStreamVideoController(UserRequest ureq, WindowControl wControl) { + protected LiveStreamVideoController(UserRequest ureq, WindowControl wControl, PlayerProfile playerProfile) { super(ureq, wControl); + this.playerProfile = playerProfile; mainVC = createVelocityContainer("video"); updateUI(ureq.getUserSession()); putInitialPanel(mainVC); @@ -74,7 +77,7 @@ public class LiveStreamVideoController extends BasicController { if (StringHelper.containsNonWhitespace(url)) { mainVC.contextPut("id", CodeHelper.getRAMUniqueID()); Streams streams = PaellaFactory.createStreams(url); - PaellaMapper paellaMapper = new PaellaMapper(streams); + PaellaMapper paellaMapper = new PaellaMapper(streams, playerProfile); MapperKey mapperKey = mapperService.register(usess, paellaMapper); mappers.add(mapperKey); String baseURI = mapperKey.getUrl(); diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewerController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewerController.java index ee2b34e63faee8ed6f4b54bc10afb79b091610fd..1beb2df9bfc7e195771ff19e17d128a844525cba 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewerController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewerController.java @@ -27,6 +27,7 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.util.UserSession; import org.olat.course.nodes.livestream.LiveStreamEvent; +import org.olat.course.nodes.livestream.paella.PlayerProfile; /** * @@ -40,12 +41,12 @@ public class LiveStreamViewerController extends BasicController { private final LiveStreamVideoController videoCtrl; private final LiveStreamMetadataController metadataCtrl; - public LiveStreamViewerController(UserRequest ureq, WindowControl wControl) { + public LiveStreamViewerController(UserRequest ureq, WindowControl wControl, PlayerProfile playerProfile) { super(ureq, wControl); mainVC = createVelocityContainer("viewer"); - videoCtrl = new LiveStreamVideoController(ureq, wControl); + videoCtrl = new LiveStreamVideoController(ureq, wControl, playerProfile); listenTo(videoCtrl); mainVC.put("video", videoCtrl.getInitialComponent()); metadataCtrl = new LiveStreamMetadataController(ureq, wControl); diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewersController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewersController.java index 63caafe9036f92d3e944739f7e7cd7ff9ba533b4..366558f124aec1f870bbdc6c9313a76e883313ab 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewersController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewersController.java @@ -30,11 +30,14 @@ 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.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.course.nodes.LiveStreamCourseNode; import org.olat.course.nodes.cal.CourseCalendars; 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.paella.PlayerProfile; import org.olat.modules.ModuleConfiguration; import org.springframework.beans.factory.annotation.Autowired; @@ -66,6 +69,8 @@ public class LiveStreamViewersController extends BasicController { private final List<DisplayWrapper> displayWrappers; private Boolean noLiveStream; + @Autowired + private LiveStreamModule liveStreamModule; @Autowired private LiveStreamService liveStreamService; @@ -76,56 +81,57 @@ public class LiveStreamViewersController extends BasicController { bufferBeforeMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_BEFORE_MIN, 0); bufferAfterMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_AFTER_MIN, 0); + PlayerProfile playerProfile = getPlayerProfile(moduleConfiguration); mainVC = createVelocityContainer("viewers"); displayWrappers = new ArrayList<>(); - displayCtrl0 = new LiveStreamViewerController(ureq, wControl); + displayCtrl0 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl0); mainVC.put("display0", displayCtrl0.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl0)); - displayCtrl1 = new LiveStreamViewerController(ureq, wControl); + displayCtrl1 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl1); mainVC.put("display1", displayCtrl1.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl1)); - displayCtrl2 = new LiveStreamViewerController(ureq, wControl); + displayCtrl2 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl2); mainVC.put("display2", displayCtrl2.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl2)); - displayCtrl3 = new LiveStreamViewerController(ureq, wControl); + displayCtrl3 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl3); mainVC.put("display3", displayCtrl3.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl3)); - displayCtrl4 = new LiveStreamViewerController(ureq, wControl); + displayCtrl4 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl4); mainVC.put("display4", displayCtrl4.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl4)); - displayCtrl5 = new LiveStreamViewerController(ureq, wControl); + displayCtrl5 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl5); mainVC.put("display5", displayCtrl5.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl5)); - displayCtrl6 = new LiveStreamViewerController(ureq, wControl); + displayCtrl6 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl6); mainVC.put("display6", displayCtrl6.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl6)); - displayCtrl7 = new LiveStreamViewerController(ureq, wControl); + displayCtrl7 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl7); mainVC.put("display7", displayCtrl7.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl7)); - displayCtrl8 = new LiveStreamViewerController(ureq, wControl); + displayCtrl8 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl8); mainVC.put("display8", displayCtrl8.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl8)); - displayCtrl9 = new LiveStreamViewerController(ureq, wControl); + displayCtrl9 = new LiveStreamViewerController(ureq, wControl, playerProfile); listenTo(displayCtrl9); mainVC.put("display9", displayCtrl9.getInitialComponent()); displayWrappers.add(new DisplayWrapper(displayCtrl9)); @@ -135,6 +141,20 @@ public class LiveStreamViewersController extends BasicController { putInitialPanel(mainVC); } + private PlayerProfile getPlayerProfile(ModuleConfiguration moduleConfiguration) { + PlayerProfile playerProfile = PlayerProfile.stream1; + + if (liveStreamModule.isMultiStreamEnabled()) { + String nodePlayerProfile = moduleConfiguration.getStringValue(LiveStreamCourseNode.CONFIG_PLAYER_PROFILE); + if (StringHelper.containsNonWhitespace(nodePlayerProfile)) { + playerProfile = PlayerProfile.valueOf(nodePlayerProfile); + } else { + playerProfile = PlayerProfile.valueOf(liveStreamModule.getPlayerProfile()); + } + } + return playerProfile; + } + void refresh(UserSession usess) { List<? extends LiveStreamEvent> events = liveStreamService.getRunningEvents(calendars, bufferBeforeMin, bufferAfterMin); putNoLiveStreamToMainVC(events); diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamsController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamsController.java index 8cf2a14417244382c9910645e5fcb42d6f99e36a..f620fc3e9cb2d099f42456caf047e4446a6f8164 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamsController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamsController.java @@ -61,7 +61,7 @@ public class LiveStreamsController extends BasicController { CourseCalendars calendars) { super(ureq, wControl); mainVC = createVelocityContainer("streams"); - + viewersCtrl = new LiveStreamViewersController(ureq, wControl, moduleConfiguration, calendars); listenTo(viewersCtrl); mainVC.put("viewers", viewersCtrl.getInitialComponent()); 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 58b6369b9b15bf3830f8bfe7547e72ac9dbe1cf4..a63505df2ee05616236168c449d799fc3e979f82 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 @@ -7,12 +7,15 @@ admin.general.title=$:\admin.menu.title admin.menu.title=Livestream admin.menu.title.alt=$:\admin.menu.title admin.module.enabled=Kursbaustein +admin.multi.stream.enabled=Multistream +admin.player.profile=Stream admin.url.separator=URL Trennzeichen admin.url.separator.help=Trennzeichen, um mehrere URLs eines einzelnen Termins zu trennen. condition.accessibility.title=Zugang 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 form.error.wrong.int=Falsches Zahlenformat. Beispiele\: 2, 10, 144 link.text=Video Livestream list.title=Anstehende Livestreams @@ -23,6 +26,9 @@ peekview.open.live=Livestream anzeigen peekview.open.upcoming=Alle anzeigen peekview.title.live=Jetzt live: {0} peekview.title.upcoming=Demn\u00E4chst: {0} +player.profile.both=Beide Streams anzeigen +player.profile.stream1=Stream 1 anzeigen +player.profile.stream2=Stream 2 anzeigen run.edit.events=Termine bearbeiten run.streams=Live Streams table.empty=Es sind keine Livestreams anstehend. 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 e1560ff39bb91a517b904f7b2f2fd4389d31d2f3..dcccc5ff9fa344a5022005fcb2171e02a8cca979 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 @@ -7,12 +7,15 @@ admin.general.title=$:\admin.menu.title admin.menu.title=Live stream admin.menu.title.alt=$:\admin.menu.title admin.module.enabled=Course element +admin.multi.stream.enabled=Multi stream +admin.player.profile=Stream admin.url.separator=URL separator admin.url.separator.help=Char / String to separate multiple URLs of a single event. 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 form.error.wrong.int=Wrong numeral format. Examples\: 2, 10, 144 link.text=Video live stream list.title=Upcoming live streams @@ -23,6 +26,9 @@ peekview.open.live=Show live stream peekview.open.upcoming=Show all peekview.title.live=Live: {0} peekview.title.upcoming=Upcoming: {0} +player.profile.both=Show both streams +player.profile.stream1=Show stream 1 +player.profile.stream2=Show stream 2 run.edit.events=Edit events run.streams=Livestreams table.empty=No upcoming live streams are available. diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index 73b483d66f1632121a4df033d456ae996e468fcc..10610f39f9ebfc87e2f582b72c845c40fb601eaf 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -1656,6 +1656,8 @@ youtube.api.key= # Options for the live stream course node ############################################################################### live.stream.enabled=false +# Enable multiple streams pre event. Actually two streams are supported. +live.stream.multi.stream.enabled=false # Char / String to separate multiple urls of a single event live.stream.url.separator=, # Buffer time to switch from the announcement page to the live stream (in minutes) @@ -1663,4 +1665,6 @@ live.stream.url.separator=, live.stream.buffer.before.min=5 live.stream.buffer.after.min=5 live.stream.edit.coach=false - +# Profile to control witch stream is visible for the watchers. +live.stream.player.profile=both +live.stream.player.profile.values=both,stream1,stream2