From 077b2f442c6c369c5b77850cc891651abd98eb2f Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Tue, 17 Dec 2019 07:28:04 +0100 Subject: [PATCH] OO-4357: Configuration of multiple stream URLs per event --- .../nodes/livestream/LiveStreamModule.java | 17 ++ .../manager/LiveStreamServiceImpl.java | 150 ----------------- .../livestream/paella/PaellaFactory.java | 45 +++++- .../nodes/livestream/paella/PaellaMapper.java | 152 +++++++++++++++--- .../nodes/livestream/paella/Stream.java | 49 ++++++ .../nodes/livestream/paella/Streams.java | 40 +++++ .../ui/LiveStreamAdminController.java | 10 ++ .../livestream/ui/LiveStreamUIFactory.java | 14 ++ .../ui/LiveStreamVideoController.java | 6 +- .../ui/_i18n/LocalStrings_de.properties | 2 + .../ui/_i18n/LocalStrings_en.properties | 2 + .../resources/serviceconfig/olat.properties | 2 + .../static/js/paella/openolat/config.json | 151 ----------------- 13 files changed, 313 insertions(+), 327 deletions(-) delete mode 100644 src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/paella/Stream.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/paella/Streams.java delete mode 100644 src/main/webapp/static/js/paella/openolat/config.json 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 7dd830efb80..cf91567e796 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamModule.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamModule.java @@ -37,12 +37,15 @@ 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_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"; @Value("${live.stream.enabled:false}") private boolean enabled; + @Value("${live.stream.url.separator:,}") + private String urlSeparator; @Value("${live.stream.buffer.before.min:5}") private int bufferBeforeMin; @Value("${live.stream.buffer.after.min:5}") @@ -62,6 +65,11 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf enabled = "true".equals(enabledObj); } + String urlSeparatorObj = getStringPropertyValue(LIVE_STREAM_URL_SEPARATOR, true); + if(StringHelper.containsNonWhitespace(urlSeparatorObj)) { + urlSeparator = urlSeparatorObj; + } + String bufferBeforeMinObj = getStringPropertyValue(LIVE_STREAM_BUFFER_BEFORE_MIN, true); if(StringHelper.containsNonWhitespace(bufferBeforeMinObj)) { bufferAfterMin = Integer.parseInt(bufferBeforeMinObj); @@ -93,6 +101,15 @@ public class LiveStreamModule extends AbstractSpringModule implements ConfigOnOf setStringProperty(LIVE_STREAM_ENABLED, Boolean.toString(enabled), true); } + public String getUrlSeparator() { + return urlSeparator; + } + + public void setUrlSeparator(String urlSeparator) { + this.urlSeparator = urlSeparator; + setStringProperty(LIVE_STREAM_URL_SEPARATOR, urlSeparator, true); + } + public int getBufferBeforeMin() { return bufferBeforeMin; } 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 deleted file mode 100644 index 5b7a52b92f5..00000000000 --- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * <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/PaellaFactory.java b/src/main/java/org/olat/course/nodes/livestream/paella/PaellaFactory.java index 4722da981c0..af8c7f3b90a 100644 --- a/src/main/java/org/olat/course/nodes/livestream/paella/PaellaFactory.java +++ b/src/main/java/org/olat/course/nodes/livestream/paella/PaellaFactory.java @@ -19,7 +19,12 @@ */ package org.olat.course.nodes.livestream.paella; +import java.util.ArrayList; +import java.util.List; + +import org.olat.core.CoreSpringFactory; import org.olat.core.util.StringHelper; +import org.olat.course.nodes.livestream.LiveStreamModule; /** * @@ -29,15 +34,49 @@ import org.olat.core.util.StringHelper; */ public class PaellaFactory { - public static Sources createSources(String url) { + public static Streams createStreams(String url) { + Streams streams = new Streams(); + if (StringHelper.containsNonWhitespace(url)) { + String[] urls = splitUrls(url); + addStreams(streams, urls); + } + return streams; + } + + private static String[] splitUrls(String url) { + String urlSeparator = CoreSpringFactory.getImpl(LiveStreamModule.class).getUrlSeparator(); + return url.split(urlSeparator); + } + + private static void addStreams(Streams streams, String[] urls) { + List<Stream> streamList = new ArrayList<>(2); + if (urls.length > 0) { + Stream stream1 = createStream("stream1", urls[0]); + streamList.add(stream1); + } + if (urls.length > 1) { + Stream stream1 = createStream("stream2", urls[1]); + streamList.add(stream1); + } + Stream[] streamArray = streamList.toArray(new Stream[streamList.size()]); + streams.setStreams(streamArray); + } + + private static Stream createStream(String content, String url) { + Stream stream = new Stream(); + stream.setContent(content); + Sources sources = createSources(url); + stream.setSources(sources); + return stream; + } + + private static Sources createSources(String url) { Sources sources = new Sources(); addSource(sources, url); return sources; } private static void addSource(Sources sources, String url) { - if (!StringHelper.containsNonWhitespace(url)) return; - String suffix = getSuffix(url); if (suffix == null) return; 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 9614911f0d2..2593b785fa1 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 @@ -48,10 +48,10 @@ public class PaellaMapper implements Mapper { private final ObjectMapper mapper = new ObjectMapper(); - private final Sources sources; + private final Streams streams; - public PaellaMapper(Sources sources) { - this.sources = sources; + public PaellaMapper(Streams streams) { + this.streams = streams; } @Override @@ -85,19 +85,11 @@ public class PaellaMapper implements Mapper { appendStaticCSS(sb, "js/paella/player/resources/style/style_dark.css"); sb.append("</head>"); sb.append("<body id=\"body\" onload=\"paella.load('playerContainer', {"); - sb.append(" configUrl: '"); - apendConfigUrl(sb); - sb.append("',"); + sb.append(" config: "); + appendPlayerConfig(sb); + sb.append(" ,"); sb.append(" data:"); - sb.append("{"); - - sb.append("'streams': [{"); - sb.append(" 'sources' : "); - sb.append(objectToJson(sources)); - sb.append(", 'content': 'stream content'"); - sb.append("}] "); - - sb.append("}"); + sb.append(objectToJson(streams)); sb.append("}"); sb.append(");\">"); sb.append("<div id=\"playerContainer\" style=\"display:block;width:100%\">"); @@ -109,11 +101,6 @@ public class PaellaMapper implements Mapper { log.debug(html); return html; } - - - private void apendConfigUrl(StringOutput sb) { - StaticMediaDispatcher.renderStaticURI(sb, "js/paella/openolat/config.json"); - } private void appendStaticJs(StringOutput sb, String javascript) { sb.append("<script src=\""); @@ -127,6 +114,131 @@ public class PaellaMapper implements Mapper { sb.append("\"></link>"); } + private void appendPlayerConfig(StringOutput sb) { + sb.append("{"); + sb.append(" 'player':{"); + sb.append(" 'accessControlClass':'paella.AccessControl',"); + sb.append(" 'profileFrameStrategy': 'paella.ProfileFrameStrategy',"); + sb.append(" 'videoQualityStrategy': 'paella.LimitedBestFitVideoQualityStrategy',"); + sb.append(" 'videoQualityStrategyParams':{ 'maxAutoQualityRes':720 },"); + sb.append(" 'reloadOnFullscreen': true,"); + sb.append(" 'videoZoom': {"); + sb.append(" 'enabled':false,"); + sb.append(" 'max':800"); + sb.append(" },"); + sb.append(" 'deprecated-methods':[{'name':'streaming','enabled':true},"); + sb.append(" {'name':'html','enabled':true},"); + sb.append(" {'name':'flash','enabled':true},"); + sb.append(" {'name':'image','enabled':true}],"); + sb.append(" 'methods':["); + sb.append(" { 'factory':'ChromaVideoFactory', 'enabled':true },"); + sb.append(" { 'factory':'WebmVideoFactory', 'enabled':true },"); + sb.append(" { 'factory':'Html5VideoFactory', 'enabled':true },"); + sb.append(" { 'factory':'MpegDashVideoFactory', 'enabled':true },"); + sb.append(" {"); + sb.append(" 'factory':'HLSVideoFactory',"); + sb.append(" 'enabled':true,"); + sb.append(" 'config': {"); + sb.append(" 'maxBufferLength': 30,"); + sb.append(" 'maxMaxBufferLength': 600,"); + sb.append(" 'maxBufferSize': 60000000,"); + sb.append(" 'maxBufferHole': 0.5,"); + sb.append(" 'lowBufferWatchdogPeriod': 0.5,"); + sb.append(" 'highBufferWatchdogPeriod': 3"); + sb.append(" },"); + sb.append(" 'iOSMaxStreams': 2,"); + sb.append(" 'androidMaxStreams': 2"); + sb.append(" }"); + sb.append(" ],"); + sb.append(" 'audioMethods':["); + sb.append(" { 'factory':'MultiformatAudioFactory', 'enabled':true }"); + sb.append(" ],"); + sb.append(" 'defaultAudioTag': '',"); + sb.append(" 'slidesMarks':{"); + sb.append(" 'enabled':true,"); + sb.append(" 'color':'gray'"); + sb.append(" }"); + sb.append(" },"); + sb.append(" 'data':{"); + sb.append(" 'enabled':true,"); + sb.append(" 'dataDelegates':{"); + sb.append(" 'trimming':'CookieDataDelegate',"); + sb.append(" 'metadata':'VideoManifestMetadataDataDelegate',"); + sb.append(" 'cameraTrack':'TrackCameraDataDelegate'"); + sb.append(" }"); + sb.append(" },"); + sb.append(" 'folders': {"); + sb.append(" 'profiles': 'config/profiles',"); + sb.append(" 'resources': '"); + StaticMediaDispatcher.renderStaticURI(sb, "js/paella/player/resources"); + sb.append("',"); + sb.append(" 'skins': 'resources/style'"); + sb.append(" },"); + sb.append(" 'experimental':{"); + sb.append(" 'autoplay':true"); + sb.append(" },"); + sb.append(" 'plugins':{"); + sb.append(" 'enablePluginsByDefault': false,"); + sb.append(" 'list':{"); + sb.append(" 'edu.harvard.dce.paella.flexSkipPlugin': {'enabled':true, 'direction': 'Rewind', 'seconds': 10, 'minWindowSize': 510 },"); + sb.append(" 'edu.harvard.dce.paella.flexSkipForwardPlugin': {'enabled':true, 'direction': 'Forward', 'seconds': 30},"); + sb.append(" 'es.upv.paella.captionsPlugin': {'enabled':true, 'searchOnCaptions':true},"); + sb.append(" 'es.upv.paella.frameControlPlugin': {'enabled': true, 'showFullPreview': 'auto', 'showCaptions':true, 'minWindowSize': 450 },"); + sb.append(" 'es.upv.paella.fullScreenButtonPlugin': {'enabled':true, 'reloadOnFullscreen':{ 'enabled':true, 'keepUserSelection':true }},"); + sb.append(" 'es.upv.paella.playPauseButtonPlugin': {'enabled':true},"); + sb.append(" 'es.upv.paella.themeChooserPlugin': {'enabled':true, 'minWindowSize': 600},"); + sb.append(" 'es.upv.paella.viewModePlugin': { 'enabled': true, 'minWindowSize': 300 },"); + sb.append(" 'es.upv.paella.volumeRangePlugin':{'enabled':true, 'showMasterVolume': true, 'showSlaveVolume': false },"); + sb.append(" 'es.upv.paella.pipModePlugin': { 'enabled':true },"); + sb.append(" 'es.upv.paella.audioSelector': { 'enabled':true, 'minWindowSize': 400 },"); + sb.append(" 'es.upv.paella.airPlayPlugin': { 'enabled':true },"); + sb.append(" 'es.upv.paella.liveStreamingIndicatorPlugin': { 'enabled': true },"); + sb.append(" 'es.upv.paella.showEditorPlugin':{'enabled':true,'alwaysVisible':true},"); + sb.append(" 'es.upv.paella.videoDataPlugin': {"); + sb.append(" 'enabled': true,"); + sb.append(" 'excludeLocations':["); + sb.append(" 'paellaplayer.upv.es'"); + sb.append(" ],"); + sb.append(" 'excludeParentLocations':["); + sb.append(" 'localhost:8000'"); + sb.append(" ]"); + sb.append(" },"); + sb.append(" 'es.upv.paella.blackBoardPlugin': {'enabled': true},"); + sb.append(" 'es.upv.paella.breaksPlayerPlugin': {'enabled': true},"); + sb.append(" 'es.upv.paella.overlayCaptionsPlugin': {'enabled': true},"); + sb.append(" 'es.upv.paella.playButtonOnScreenPlugin': {'enabled':true},"); + 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(" }"); + sb.append(" }"); + sb.append(" },"); + sb.append(" 'defaultProfile':'presenter_presentation',"); + sb.append(" 'standalone' : {"); + sb.append(" 'repository': '../repository/'"); + sb.append(" },"); + sb.append(" 'skin': {"); + sb.append(" 'available': ["); + sb.append(" 'dark',"); + sb.append(" 'dark_small',"); + sb.append(" 'light',"); + sb.append(" 'light_small'"); + sb.append(" ]"); + sb.append(" }"); + sb.append("}"); + } + private String objectToJson(Object o) { String json = null; try { diff --git a/src/main/java/org/olat/course/nodes/livestream/paella/Stream.java b/src/main/java/org/olat/course/nodes/livestream/paella/Stream.java new file mode 100644 index 00000000000..f3adc58067c --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/paella/Stream.java @@ -0,0 +1,49 @@ +/** + * <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; + +/** + * + * Initial date: 16 Dec 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class Stream { + + private Sources sources; + private String content; + + public Sources getSources() { + return sources; + } + + public void setSources(Sources sources) { + this.sources = sources; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/paella/Streams.java b/src/main/java/org/olat/course/nodes/livestream/paella/Streams.java new file mode 100644 index 00000000000..ea4d64f808d --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/paella/Streams.java @@ -0,0 +1,40 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.paella; + +/** + * + * Initial date: 16 Dec 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class Streams { + + private Stream[] streams; + + public Stream[] getStreams() { + return streams; + } + + public void setStreams(Stream[] streams) { + this.streams = streams; + } + +} 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 3acd268aa8b..3542060814b 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 @@ -21,6 +21,7 @@ package org.olat.course.nodes.livestream.ui; 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.FormItemContainer; @@ -44,6 +45,7 @@ public class LiveStreamAdminController extends FormBasicController { private static final String[] ENABLED_KEYS = new String[]{"on"}; private MultipleSelectionElement enabledEl; + private TextElement urlSeparatorEl; private TextElement bufferBeforeMinEl; private TextElement bufferAfterMinEl; private MultipleSelectionElement coachCanEditEl; @@ -68,6 +70,10 @@ public class LiveStreamAdminController extends FormBasicController { translateAll(getTranslator(), ENABLED_KEYS)); enabledEl.select(ENABLED_KEYS[0], liveStreamModule.isEnabled()); + 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")); @@ -99,6 +105,7 @@ public class LiveStreamAdminController extends FormBasicController { protected boolean validateFormLogic(UserRequest ureq) { boolean allOk = super.validateFormLogic(ureq); + allOk &= validateMandatory(urlSeparatorEl); allOk &= validateInteger(bufferBeforeMinEl, true); allOk &= validateInteger(bufferAfterMinEl, true); @@ -110,6 +117,9 @@ public class LiveStreamAdminController extends FormBasicController { boolean enabled = enabledEl.isAtLeastSelected(1); liveStreamModule.setEnabled(enabled); + String urlSeparator = urlSeparatorEl.getValue(); + liveStreamModule.setUrlSeparator(urlSeparator); + int bufferBeforeMin = Integer.parseInt(bufferBeforeMinEl.getValue()); liveStreamModule.setBufferBeforeMin(bufferBeforeMin); diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamUIFactory.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamUIFactory.java index 6dbf4e41848..73fd1a6c4e7 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamUIFactory.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamUIFactory.java @@ -29,6 +29,20 @@ import org.olat.core.util.StringHelper; * */ class LiveStreamUIFactory { + + static boolean validateMandatory(TextElement el) { + boolean allOk = true; + el.clearError(); + if(el.isEnabled() && el.isVisible()) { + String val = el.getValue(); + if (!StringHelper.containsNonWhitespace(val)) { + el.setErrorKey("form.mandatory.hover", null); + allOk = false; + } + } + return allOk; + } + static boolean validateInteger(TextElement el, boolean mandatory) { boolean allOk = true; 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 b8c1d4b8fac..9ea0d462e78 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,7 +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.Sources; +import org.olat.course.nodes.livestream.paella.Streams; import org.springframework.beans.factory.annotation.Autowired; /** @@ -73,8 +73,8 @@ public class LiveStreamVideoController extends BasicController { private void updateUI(UserSession usess) { if (StringHelper.containsNonWhitespace(url)) { mainVC.contextPut("id", CodeHelper.getRAMUniqueID()); - Sources sources = PaellaFactory.createSources(url); - PaellaMapper paellaMapper = new PaellaMapper(sources); + Streams streams = PaellaFactory.createStreams(url); + PaellaMapper paellaMapper = new PaellaMapper(streams); 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/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties index ae3522b212f..58b6369b9b1 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,6 +7,8 @@ admin.general.title=$:\admin.menu.title admin.menu.title=Livestream admin.menu.title.alt=$:\admin.menu.title admin.module.enabled=Kursbaustein +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) 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 7abc980a66c..e1560ff39bb 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,6 +7,8 @@ admin.general.title=$:\admin.menu.title admin.menu.title=Live stream admin.menu.title.alt=$:\admin.menu.title admin.module.enabled=Course element +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) diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index e9ccf88cc1a..73b483d66f1 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 +# 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) # and vice versa. live.stream.buffer.before.min=5 diff --git a/src/main/webapp/static/js/paella/openolat/config.json b/src/main/webapp/static/js/paella/openolat/config.json deleted file mode 100644 index f6452d3f19a..00000000000 --- a/src/main/webapp/static/js/paella/openolat/config.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "player":{ - "accessControlClass":"paella.AccessControl", - "profileFrameStrategy": "paella.ProfileFrameStrategy", - "videoQualityStrategy": "paella.LimitedBestFitVideoQualityStrategy", - "videoQualityStrategyParams":{ "maxAutoQualityRes":720 }, - "reloadOnFullscreen": true, - "videoZoom": { - "enabled":false, - "max":800 - }, - - "deprecated-methods":[{"name":"streaming","enabled":true}, - {"name":"html","enabled":true}, - {"name":"flash","enabled":true}, - {"name":"image","enabled":true}], - - "methods":[ - { "factory":"ChromaVideoFactory", "enabled":true }, - { "factory":"WebmVideoFactory", "enabled":true }, - { "factory":"Html5VideoFactory", "enabled":true }, - { "factory":"MpegDashVideoFactory", "enabled":true }, - { - "factory":"HLSVideoFactory", - "enabled":true, - "config": { - "*** You can add more hls.js settings here": "", - "https://github.com/video-dev/hls.js/blob/master/docs/API.md": "", - "maxBufferLength": 30, - "maxMaxBufferLength": 600, - "maxBufferSize": 60000000, - "maxBufferHole": 0.5, - "lowBufferWatchdogPeriod": 0.5, - "highBufferWatchdogPeriod": 3 - }, - "iOSMaxStreams": 2, - "androidMaxStreams": 2 - }, - { "factory":"RTMPVideoFactory", "enabled":true }, - { "factory":"ImageVideoFactory", "enabled":true }, - { "factory":"YoutubeVideoFactory", "enabled":true }, - { "factory":"Video360ThetaFactory", "enabled":true }, - { "factory":"Video360Factory", "enabled":true } - ], - "audioMethods":[ - { "factory":"MultiformatAudioFactory", "enabled":true } - ], - "defaultAudioTag": "", - "slidesMarks":{ - "enabled":true, - "color":"gray" - } - }, - "defaultProfile":"presenter_presentation", - "data":{ - "enabled":true, - "dataDelegates":{ - "trimming":"CookieDataDelegate", - "metadata":"VideoManifestMetadataDataDelegate", - "cameraTrack":"TrackCameraDataDelegate" - } - }, - "folders": { - "profiles": "config/profiles", - "resources": "resources", - "skins": "resources/style" - }, - "experimental":{ - "autoplay":true - }, - "plugins":{ - "enablePluginsByDefault": false, - - "//**** Instructions: Disable any individual plugin by setting its enable property to false": {"enabled": false}, - "//**** For a list of available plugins and configuration, go to": "https://github.com/polimediaupv/paella/blob/master/doc/plugins.md", - "list":{ - "//******* Button plugins": "", - "edu.harvard.dce.paella.flexSkipPlugin": {"enabled":true, "direction": "Rewind", "seconds": 10, "minWindowSize": 510 }, - "edu.harvard.dce.paella.flexSkipForwardPlugin": {"enabled":true, "direction": "Forward", "seconds": 30}, - "es.upv.paella.captionsPlugin": {"enabled":true, "searchOnCaptions":true}, - "es.upv.paella.frameControlPlugin": {"enabled": true, "showFullPreview": "auto", "showCaptions":true, "minWindowSize": 450 }, - "es.upv.paella.fullScreenButtonPlugin": {"enabled":true, "reloadOnFullscreen":{ "enabled":true, "keepUserSelection":true }}, - "es.upv.paella.multipleQualitiesPlugin": {"enabled":true, "showWidthRes":true, "minWindowSize": 550 }, - "es.upv.paella.playPauseButtonPlugin": {"enabled":true}, - "es.upv.paella.themeChooserPlugin": {"enabled":true, "minWindowSize": 600}, - "es.upv.paella.viewModePlugin": { "enabled": true, "minWindowSize": 300 }, - "es.upv.paella.volumeRangePlugin":{"enabled":true, "showMasterVolume": true, "showSlaveVolume": false }, - "es.upv.paella.pipModePlugin": { "enabled":true }, - "es.upv.paella.audioSelector": { "enabled":true, "minWindowSize": 400 }, - "es.upv.paella.airPlayPlugin": { "enabled":true }, - - "//***** Video Overlay Button Plugins": "", - "es.upv.paella.liveStreamingIndicatorPlugin": { "enabled": true }, - "es.upv.paella.showEditorPlugin":{"enabled":true,"alwaysVisible":true}, - "es.upv.paella.arrowSlidesNavigatorPlugin": {"enabled": true, "content":["presentation","presenter"] }, - "es.upv.paella.videoDataPlugin": { - "enabled": true, - "excludeLocations":[ - "paellaplayer.upv.es" - ], - "excludeParentLocations":[ - "localhost:8000" - ] - }, - - "//**** Event Driven Plugins": "", - "es.upv.paella.blackBoardPlugin": {"enabled": true}, - "es.upv.paella.breaksPlayerPlugin": {"enabled": true}, - "es.upv.paella.overlayCaptionsPlugin": {"enabled": true}, - "es.upv.paella.playButtonOnScreenPlugin": {"enabled":true}, - "es.upv.paella.translecture.captionsPlugin": {"enabled":true}, - "es.upv.paella.trimmingPlayerPlugin": {"enabled":true}, - "es.upv.paella.windowTitlePlugin": {"enabled": true}, - - "//**** Video profile plugins": "", - "es.upv.paella.singleStreamProfilePlugin": { - "enabled": true, - "videoSets": [ - { "icon":"professor_icon.svg", "id":"presenter", "content":["presenter"]}, - { "icon":"slide_icon.svg", "id":"presentation", "content":["presentation"]} - ] - - }, - "es.upv.paella.dualStreamProfilePlugin": { "enabled":true, - "videoSets": [ - { "icon":"slide_professor_icon.svg", "id":"presenter_presentation", "content":["presenter","presentation"] }, - { "icon":"slide_professor_icon.svg", "id":"presenter2_presentation", "content":["presenter-2","presentation"] }, - { "icon":"slide_professor_icon.svg", "id":"presenter3_presentation", "content":["presenter-3","presentation"] } - ] - }, - "es.upv.paella.tripleStreamProfilePlugin": { - "enabled": true, - "videoSets": [ - { "icon":"three_streams_icon.svg", "id":"presenter_presentation_presenter2", "content":["presenter","presentation","presenter-2"] }, - { "icon":"three_streams_icon.svg", "id":"presenter_presentation_presenter3", "content":["presenter","presentation","presenter-3"] } - ] - } - } - }, - "standalone" : { - "repository": "../repository/" - }, - "skin": { - "available": [ - "dark", - "dark_small", - "light", - "light_small" - ] - } -} -- GitLab