From 5b9afdb5f46d462e550fe247a508e7a41bd0d9f9 Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Wed, 29 May 2019 16:45:49 +0200 Subject: [PATCH] OO-4043: Automatic GUI update ("push") when live stream has started, ended or changed --- .../nodes/livestream/LiveStreamEvent.java | 4 + .../manager/LIveStreamServiceImpl.java | 2 + .../livestream/model/LiveStreamEventImpl.java | 46 +++ .../ui/LiveStreamMetadataController.java | 115 +++++++ .../ui/LiveStreamVideoController.java | 78 +++++ .../ui/LiveStreamViewerController.java | 111 ++----- .../ui/LiveStreamViewersController.java | 303 ++++++++++++++++++ .../livestream/ui/LiveStreamsController.java | 36 ++- .../livestream/ui/_content/metadata.html | 29 ++ .../nodes/livestream/ui/_content/streams.html | 2 +- .../nodes/livestream/ui/_content/video.html | 10 + .../nodes/livestream/ui/_content/viewer.html | 44 +-- .../nodes/livestream/ui/_content/viewers.html | 18 ++ 13 files changed, 656 insertions(+), 142 deletions(-) create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamVideoController.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewersController.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/_content/metadata.html create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/_content/video.html create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/_content/viewers.html diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamEvent.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamEvent.java index ec304b7fc15..680225310f2 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamEvent.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamEvent.java @@ -28,6 +28,8 @@ import java.util.Date; * */ public interface LiveStreamEvent { + + String getId(); String getSubject(); @@ -37,6 +39,8 @@ public interface LiveStreamEvent { Date getEnd(); + boolean isAllDayEvent(); + String getLocation(); String getLiveStreamUrl(); 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 490e9b6e90b..ff9ec538050 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 @@ -114,8 +114,10 @@ public class LIveStreamServiceImpl implements LiveStreamService { private LiveStreamEventImpl toLiveStreamEvent(KalendarEvent event, boolean timeOnly) { LiveStreamEventImpl liveStreamEvent = new LiveStreamEventImpl(); + liveStreamEvent.setId(event.getID()); liveStreamEvent.setBegin(event.getBegin()); liveStreamEvent.setEnd(event.getEnd()); + liveStreamEvent.setAllDayEvent(event.isAllDayEvent()); liveStreamEvent.setLiveStreamUrl(event.getLiveStreamUrl()); if (!timeOnly) { liveStreamEvent.setSubject(event.getSubject()); diff --git a/src/main/java/org/olat/course/nodes/livestream/model/LiveStreamEventImpl.java b/src/main/java/org/olat/course/nodes/livestream/model/LiveStreamEventImpl.java index a73cde75bd5..0a5f6219152 100644 --- a/src/main/java/org/olat/course/nodes/livestream/model/LiveStreamEventImpl.java +++ b/src/main/java/org/olat/course/nodes/livestream/model/LiveStreamEventImpl.java @@ -32,13 +32,25 @@ import org.olat.course.nodes.livestream.LiveStreamEvent; */ public class LiveStreamEventImpl implements LiveStreamEvent { + private String id; private String subject; private String description; private Date begin; private Date end; + private boolean allDayEvent; private String location; private String liveStreamUrl; + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + @Override public String getSubject() { return subject; @@ -80,6 +92,15 @@ public class LiveStreamEventImpl implements LiveStreamEvent { return location; } + @Override + public boolean isAllDayEvent() { + return allDayEvent; + } + + public void setAllDayEvent(boolean allDayEvent) { + this.allDayEvent = allDayEvent; + } + public void setLocation(String location) { this.location = location; } @@ -93,4 +114,29 @@ public class LiveStreamEventImpl implements LiveStreamEvent { this.liveStreamUrl = liveStreamUrl; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.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; + LiveStreamEventImpl other = (LiveStreamEventImpl) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + } diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java new file mode 100644 index 00000000000..e35dba8a8b4 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java @@ -0,0 +1,115 @@ +/** + * <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.Calendar; +import java.util.Date; +import java.util.Locale; + +import org.apache.commons.lang.time.DateUtils; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarUtils; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +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.Formatter; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.course.nodes.livestream.LiveStreamEvent; + +/** + * + * Initial date: 29 May 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class LiveStreamMetadataController extends BasicController { + + private final VelocityContainer mainVC; + + private LiveStreamEvent currentEvent; + + protected LiveStreamMetadataController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, Util.createPackageTranslator(CalendarManager.class, ureq.getLocale())); + mainVC = createVelocityContainer("metadata"); + updateUI(null); + putInitialPanel(mainVC); + } + + public void setEvent(LiveStreamEvent event) { + updateUI(event); + } + + private void updateUI(LiveStreamEvent event) { + if (event == null || !event.equals(currentEvent)) { + currentEvent = event; + if (event != null) { + mainVC.contextPut("id", event.getId()); + mainVC.contextPut("title", event.getSubject()); + addDateToMainVC(event); + StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(event.getDescription())); + mainVC.contextPut("description", description.toString()); + if (StringHelper.containsNonWhitespace(event.getLocation())) { + mainVC.contextPut("location", event.getLocation()); + } + } else { + mainVC.contextRemove("id"); + } + } + } + + private String addDateToMainVC(LiveStreamEvent calEvent) { + Locale locale = getLocale(); + Calendar cal = CalendarUtils.createCalendarInstance(locale); + Date begin = calEvent.getBegin(); + Date end = calEvent.getEnd(); + cal.setTime(begin); + + StringBuilder sb = new StringBuilder(); + sb.append(StringHelper.formatLocaleDateFull(begin.getTime(), locale)); + mainVC.contextPut("date", sb.toString()); + + if (!calEvent.isAllDayEvent()) { + sb = new StringBuilder(); + sb.append(StringHelper.formatLocaleTime(begin.getTime(), locale)); + sb.append(" - "); + if (!DateUtils.isSameDay(begin, end)) { + sb.append(StringHelper.formatLocaleDateFull(end.getTime(), locale)).append(", "); + } + sb.append(StringHelper.formatLocaleTime(end.getTime(), locale)); + mainVC.contextPut("time", sb.toString()); + } + return sb.toString(); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void doDispose() { + // + } + +} 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 new file mode 100644 index 00000000000..7e70902eec6 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamVideoController.java @@ -0,0 +1,78 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +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.CodeHelper; +import org.olat.core.util.StringHelper; +import org.olat.course.nodes.livestream.LiveStreamEvent; + +/** + * + * Initial date: 29 May 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class LiveStreamVideoController extends BasicController { + + private final VelocityContainer mainVC; + + private String runningUrl; + + protected LiveStreamVideoController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + mainVC = createVelocityContainer("video"); + updateUI(null); + putInitialPanel(mainVC); + } + + public void setEvent(LiveStreamEvent event) { + String url = event != null? event.getLiveStreamUrl(): null; + updateUI(url); + } + + private void updateUI(String url) { + if (url == null || !url.equalsIgnoreCase(runningUrl)) { + runningUrl = url; + if (StringHelper.containsNonWhitespace(runningUrl)) { + mainVC.contextPut("id", CodeHelper.getRAMUniqueID()); + mainVC.contextPut("src", url); + } else { + mainVC.contextRemove("id"); + } + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void doDispose() { + // + } + +} 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 9fe0edb36b1..208b8b35fe2 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 @@ -19,119 +19,44 @@ */ package org.olat.course.nodes.livestream.ui; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.Locale; - -import org.apache.commons.lang.time.DateUtils; -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarUtils; -import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarEvent; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; 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.CodeHelper; -import org.olat.core.util.Formatter; -import org.olat.core.util.StringHelper; -import org.olat.core.util.Util; -import org.olat.course.nodes.LiveStreamCourseNode; -import org.olat.course.nodes.cal.CourseCalendars; -import org.olat.modules.ModuleConfiguration; +import org.olat.course.nodes.livestream.LiveStreamEvent; /** * - * Initial date: 24 May 2019<br> + * Initial date: 29 May 2019<br> * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com * */ public class LiveStreamViewerController extends BasicController { - - private final VelocityContainer mainVC; - - private final CourseCalendars calendars; - private final int bufferBeforeMin; - private final int bufferAfterMin; - public LiveStreamViewerController(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfiguration, - CourseCalendars calendars) { - super(ureq, wControl, Util.createPackageTranslator(CalendarManager.class, ureq.getLocale())); - this.calendars = calendars; - - bufferBeforeMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_BEFORE_MIN, 0); - bufferAfterMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_AFTER_MIN, 0); + private final VelocityContainer mainVC; + private final LiveStreamVideoController videoCtrl; + private final LiveStreamMetadataController metadataCtrl; + + public LiveStreamViewerController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); mainVC = createVelocityContainer("viewer"); - KalendarEvent liveStreamEvent = getActiveLiveStream(); - if (liveStreamEvent == null) { - mainVC.contextPut("noLiveStream", Boolean.TRUE); - } else { - mainVC.contextPut("id", CodeHelper.getRAMUniqueID()); - mainVC.contextPut("src", liveStreamEvent.getLiveStreamUrl()); - mainVC.contextPut("title", liveStreamEvent.getSubject()); - addDateToMainVC(liveStreamEvent); - StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(liveStreamEvent.getDescription())); - mainVC.contextPut("description", description.toString()); - if (StringHelper.containsNonWhitespace(liveStreamEvent.getLocation())) { - mainVC.contextPut("location", liveStreamEvent.getLocation()); - } - } - putInitialPanel(mainVC); - } - - private KalendarEvent getActiveLiveStream() { - for (KalendarRenderWrapper calendar : calendars.getCalendars()) { - Kalendar cal = calendar.reloadKalendar(); - Collection<KalendarEvent> events = cal.getEvents(); - for (KalendarEvent event : events) { - if (isActiveLiveStream(event)) { - return event; - } - } - } - return null; - } - - private boolean isActiveLiveStream(KalendarEvent event) { - if (event.getLiveStreamUrl() == null) return false; + videoCtrl = new LiveStreamVideoController(ureq, wControl); + listenTo(videoCtrl); + mainVC.put("video", videoCtrl.getInitialComponent()); + metadataCtrl = new LiveStreamMetadataController(ureq, wControl); + listenTo(metadataCtrl); + mainVC.put("metadata", metadataCtrl.getInitialComponent()); - Instant now = Instant.now(); - Instant startWithBuffer = event.getBegin().toInstant().minus(Duration.of(bufferBeforeMin, ChronoUnit.MINUTES)); - Instant endWithBuffer = event.getEnd().toInstant().plus(Duration.of(bufferAfterMin, ChronoUnit.MINUTES)); - return now.isAfter(startWithBuffer) && now.isBefore(endWithBuffer); + putInitialPanel(mainVC); } - - private String addDateToMainVC(KalendarEvent calEvent) { - Locale locale = getLocale(); - Calendar cal = CalendarUtils.createCalendarInstance(locale); - Date begin = calEvent.getBegin(); - Date end = calEvent.getEnd(); - cal.setTime(begin); - - StringBuilder sb = new StringBuilder(); - sb.append(StringHelper.formatLocaleDateFull(begin.getTime(), locale)); - mainVC.contextPut("date", sb.toString()); - if (!calEvent.isAllDayEvent()) { - sb = new StringBuilder(); - sb.append(StringHelper.formatLocaleTime(begin.getTime(), locale)); - sb.append(" - "); - if (!DateUtils.isSameDay(begin, end)) { - sb.append(StringHelper.formatLocaleDateFull(end.getTime(), locale)).append(", "); - } - sb.append(StringHelper.formatLocaleTime(end.getTime(), locale)); - mainVC.contextPut("time", sb.toString()); - } - return sb.toString(); + public void setEvent(LiveStreamEvent event) { + videoCtrl.setEvent(event); + metadataCtrl.setEvent(event); } @Override 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 new file mode 100644 index 00000000000..720d16e9961 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamViewersController.java @@ -0,0 +1,303 @@ +/** + * <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.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +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.course.nodes.LiveStreamCourseNode; +import org.olat.course.nodes.cal.CourseCalendars; +import org.olat.course.nodes.livestream.LiveStreamEvent; +import org.olat.course.nodes.livestream.LiveStreamService; +import org.olat.modules.ModuleConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 24 May 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class LiveStreamViewersController extends BasicController { + + private final VelocityContainer mainVC; + + private final LiveStreamViewerController displayCtrl0; + private final LiveStreamViewerController displayCtrl1; + private final LiveStreamViewerController displayCtrl2; + private final LiveStreamViewerController displayCtrl3; + private final LiveStreamViewerController displayCtrl4; + private final LiveStreamViewerController displayCtrl5; + private final LiveStreamViewerController displayCtrl6; + private final LiveStreamViewerController displayCtrl7; + private final LiveStreamViewerController displayCtrl8; + private final LiveStreamViewerController displayCtrl9; + + private final CourseCalendars calendars; + private final int bufferBeforeMin; + private final int bufferAfterMin; + + private final List<DisplayWrapper> displayWrappers; + private Boolean noLiveStream; + + @Autowired + private LiveStreamService liveStreamService; + + public LiveStreamViewersController(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfiguration, + CourseCalendars calendars) { + super(ureq, wControl); + this.calendars = calendars; + + bufferBeforeMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_BEFORE_MIN, 0); + bufferAfterMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_AFTER_MIN, 0); + + mainVC = createVelocityContainer("viewers"); + + displayWrappers = new ArrayList<>(); + displayCtrl0 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl0); + mainVC.put("display0", displayCtrl0.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl0)); + + displayCtrl1 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl1); + mainVC.put("display1", displayCtrl1.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl1)); + + displayCtrl2 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl2); + mainVC.put("display2", displayCtrl2.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl2)); + + displayCtrl3 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl3); + mainVC.put("display3", displayCtrl3.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl3)); + + displayCtrl4 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl4); + mainVC.put("display4", displayCtrl4.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl4)); + + displayCtrl5 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl5); + mainVC.put("display5", displayCtrl5.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl5)); + + displayCtrl6 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl6); + mainVC.put("display6", displayCtrl6.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl6)); + + displayCtrl7 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl7); + mainVC.put("display7", displayCtrl7.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl7)); + + displayCtrl8 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl8); + mainVC.put("display8", displayCtrl8.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl8)); + + displayCtrl9 = new LiveStreamViewerController(ureq, wControl); + listenTo(displayCtrl9); + mainVC.put("display9", displayCtrl9.getInitialComponent()); + displayWrappers.add(new DisplayWrapper(displayCtrl9)); + + refresh(); + + putInitialPanel(mainVC); + } + + void refresh() { + List<? extends LiveStreamEvent> events = liveStreamService.getRunningEvents(calendars, bufferBeforeMin, bufferAfterMin); + putNoLiveStreamToMainVC(events); + + // This component should not get dirty, if a new live stream is started or ended to + // avoid restart of other running live streams. So we hat some slots. If a new + // live stream starts, it is put to the next free slot. If no slot is left, bad + // luck. + events = removeOverlappingWithSameUrl(events); + Collections.sort(events, (e1, e2) -> e1.getBegin().compareTo(e2.getBegin())); + displayStartedEvents(events); + removeEndedEvents(events); + } + + private void putNoLiveStreamToMainVC(List<? extends LiveStreamEvent> events) { + Boolean newNoLiveStream = events.isEmpty()? Boolean.TRUE: Boolean.FALSE; + if (!newNoLiveStream.equals(noLiveStream)) { + noLiveStream = newNoLiveStream; + mainVC.contextPut("noLiveStream", noLiveStream); + } + } + + private List<? extends LiveStreamEvent> removeOverlappingWithSameUrl(List<? extends LiveStreamEvent> events) { + List<LiveStreamEvent> remainingEvents = new ArrayList<>(); + List<LiveStreamEvent> removedEvents = new ArrayList<>(); + for (LiveStreamEvent event: events) { + if (!removedEvents.contains(event)) { + List<? extends LiveStreamEvent> sameUrlEvents = getEventsWithSameUrl(events, event); + if (sameUrlEvents.size() > 1) { + LiveStreamEvent runningEvent = getExcatlyRunning(sameUrlEvents); + if (runningEvent != null) { + for (LiveStreamEvent sameUrlEvent : sameUrlEvents) { + if (runningEvent.equals(sameUrlEvent)) { + remainingEvents.add(sameUrlEvent); + } else { + removedEvents.add(sameUrlEvent); + } + } + } + } else { + remainingEvents.add(event); + } + } + } + return remainingEvents; + } + + private LiveStreamEvent getExcatlyRunning(List<? extends LiveStreamEvent> events) { + Collections.sort(events, (e1, e2) -> e1.getBegin().compareTo(e2.getBegin())); + Date now = new Date(); + for (LiveStreamEvent event: events) { + if (event.getEnd().after(now)) { + return event; + } + } + return null; + } + + private List<? extends LiveStreamEvent> getEventsWithSameUrl(List<? extends LiveStreamEvent> events, + LiveStreamEvent event) { + List<LiveStreamEvent> sameUrlEvents = new ArrayList<>(); + for (LiveStreamEvent liveStreamEvent : events) { + if (liveStreamEvent.getLiveStreamUrl().equalsIgnoreCase(event.getLiveStreamUrl())) { + sameUrlEvents.add(liveStreamEvent); + } + } + return sameUrlEvents; + } + + private void displayStartedEvents(List<? extends LiveStreamEvent> events) { + for (LiveStreamEvent event: events) { + DisplayWrapper displayWrapper = getDisplayWrapper(event); + if (displayWrapper != null) { + updateEvent(displayWrapper, event); + } else { + addToNextDisplay(event); + } + } + } + + private DisplayWrapper getDisplayWrapper(LiveStreamEvent event) { + for (DisplayWrapper displayWrapper : displayWrappers) { + if (displayWrapper.getEvent() != null && displayWrapper.getEvent().getLiveStreamUrl().equals(event.getLiveStreamUrl())) { + return displayWrapper; + } + } + return null; + } + + private void addToNextDisplay(LiveStreamEvent event) { + DisplayWrapper nextDisplay = getNextFreeDisplay(); + if (nextDisplay != null ) { + updateEvent(nextDisplay, event); + } + } + + private DisplayWrapper getNextFreeDisplay() { + DisplayWrapper nextDisplay = null; + for (int i = displayWrappers.size() - 1; i >= 0; i--) { + if (displayWrappers.get(i).getEvent() == null) { + nextDisplay = displayWrappers.get(i); + } else { + return nextDisplay; + } + } + return nextDisplay; + } + + private void removeEndedEvents(List<? extends LiveStreamEvent> events) { + for (DisplayWrapper displayWrapper : displayWrappers) { + LiveStreamEvent wrappedEvent = displayWrapper.getEvent(); + if (hasEnded(wrappedEvent, events)) { + updateEvent(displayWrapper, null); + } + } + } + + private boolean hasEnded(LiveStreamEvent event, List<? extends LiveStreamEvent> runningEvents) { + if (event == null) return false; + + for (LiveStreamEvent runningEvent : runningEvents) { + if (runningEvent.getLiveStreamUrl().equals(event.getLiveStreamUrl())) { + return false; + } + } + return true; + } + + private void updateEvent(DisplayWrapper displayWrapper, LiveStreamEvent event) { + displayWrapper.setEvent(event); + displayWrapper.getController().setEvent(event); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void doDispose() { + // + } + + private static class DisplayWrapper { + + private final LiveStreamViewerController controller; + private LiveStreamEvent event; + + private DisplayWrapper(LiveStreamViewerController controller) { + this.controller = controller; + } + + public LiveStreamViewerController getController() { + return controller; + } + + public LiveStreamEvent getEvent() { + return event; + } + + public void setEvent(LiveStreamEvent event) { + this.event = event; + } + + } + +} 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 af7fdbdd116..10cb78966db 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 @@ -19,12 +19,18 @@ */ package org.olat.course.nodes.livestream.ui; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Logger; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; 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.logging.Tracing; import org.olat.course.nodes.cal.CourseCalendars; import org.olat.modules.ModuleConfiguration; @@ -36,28 +42,37 @@ import org.olat.modules.ModuleConfiguration; */ public class LiveStreamsController extends BasicController { + private static final Logger log = Tracing.createLoggerFor(LiveStreamsController.class); + private final VelocityContainer mainVC; - private LiveStreamViewerController viewerCtrl; + private LiveStreamViewersController viewersCtrl; private LiveStreamListController listCtrl; + private ScheduledExecutorService scheduler; + public LiveStreamsController(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfiguration, CourseCalendars calendars) { super(ureq, wControl); mainVC = createVelocityContainer("streams"); - viewerCtrl = new LiveStreamViewerController(ureq, wControl, moduleConfiguration, calendars); - listenTo(viewerCtrl); - mainVC.put("viewer", viewerCtrl.getInitialComponent()); + viewersCtrl = new LiveStreamViewersController(ureq, wControl, moduleConfiguration, calendars); + listenTo(viewersCtrl); + mainVC.put("viewers", viewersCtrl.getInitialComponent()); listCtrl = new LiveStreamListController(ureq, wControl, moduleConfiguration, calendars); listenTo(listCtrl); mainVC.put("list", listCtrl.getInitialComponent()); + + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(new RefreshTask(), 10, 10, TimeUnit.SECONDS); putInitialPanel(mainVC); } - public void refreshData() { + public synchronized void refreshData() { + log.debug("Refresh live stream data of " + getIdentity()); + viewersCtrl.refresh(); listCtrl.refreshData(); } @@ -68,7 +83,16 @@ public class LiveStreamsController extends BasicController { @Override protected void doDispose() { - // + scheduler.shutdown(); + } + + private final class RefreshTask implements Runnable { + + @Override + public void run() { + refreshData(); + } + } } diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/metadata.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/metadata.html new file mode 100644 index 00000000000..090dfe937df --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/metadata.html @@ -0,0 +1,29 @@ +#if($r.isNotNull($id)) + <div class="o_block_large_bottom clearfix"> + #if($title) + <h3>$title</h3> + #end + + <div class="o_cal_date text-muted"> + <i class="o_icon o_icon-fw o_icon_calendar"> </i> + $date + </div> + #if($time && $time != "") + <div class="o_cal_time text-muted"> + <i class="o_icon o_icon-fw o_icon_time"> </i> + $time + </div> + #end + #if($location && !${location.isEmpty()}) + <div class="o_cal_location text-muted"> + <i class="o_icon o_icon-fw o_icon_home" title="$r.translateInAttribute("cal.form.location")"> </i> + $r.escapeHtml($location) + </div> + #end + #if($description && !${description.isEmpty()}) + <div class="o_cal_description"> + $r.xssScan($description) + </div> + #end + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/streams.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/streams.html index a9da74cd7e2..daadf3665d1 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/_content/streams.html +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/streams.html @@ -1,2 +1,2 @@ -$r.render("viewer") +$r.render("viewers") $r.render("list") diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/video.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/video.html new file mode 100644 index 00000000000..913c8b3df55 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/video.html @@ -0,0 +1,10 @@ +#if($r.isNotNull($id)) + <div> + ## height / width does not matter, because the size is streched responsive. + <p><span id="o_livestream_${id}" class="olatFlashMovieViewer" style="display:block;border:solid 1px #000;"> + <script type="text/javascript" defer> + BPlayer.insertPlayer("$src","o_livestream_${id}",400,'100%',0,0,"video",undefined,true,true,false); + </script> + </span></p> + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewer.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewer.html index 613341a015b..691b46d7395 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewer.html +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewer.html @@ -1,42 +1,2 @@ -<div class="o_livestream_viewer o_block_large_bottom clearfix"> - #if($noLiveStream) - <div class="o_warning"> - $r.translate("viewer.no.stream") - </div> - #else - <div class="o_viewer"> - ## height / width does not matter, because the size is streched responsive. - <p><span id="o_livestream_${id}" class="olatFlashMovieViewer" style="display:block;border:solid 1px #000;"> - <script type="text/javascript" defer> - BPlayer.insertPlayer("$src","o_livestream_${id}",400,'100%',0,0,"video",undefined,true,true,false); - </script> - </span></p> - </div> - - #if($title) - <h3>$title</h3> - #end - - <div class="o_cal_date text-muted"> - <i class="o_icon o_icon-fw o_icon_calendar"> </i> - $date - </div> - #if($time && $time != "") - <div class="o_cal_time text-muted"> - <i class="o_icon o_icon-fw o_icon_time"> </i> - $time - </div> - #end - #if($location && !${location.isEmpty()}) - <div class="o_cal_location text-muted"> - <i class="o_icon o_icon-fw o_icon_home" title="$r.translateInAttribute("cal.form.location")"> </i> - $r.escapeHtml($location) - </div> - #end - #if($description && !${description.isEmpty()}) - <div class="o_cal_description"> - $r.xssScan($description) - </div> - #end - #end -</div> \ No newline at end of file +$r.render("video") +$r.render("metadata") diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewers.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewers.html new file mode 100644 index 00000000000..9a83dc40fe0 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/viewers.html @@ -0,0 +1,18 @@ +<div class="o_livestream_viewer"> + #if($noLiveStream) + <div class="o_warning"> + $r.translate("viewer.no.stream") + </div> + #else + $r.render("display0") + $r.render("display1") + $r.render("display2") + $r.render("display3") + $r.render("display4") + $r.render("display5") + $r.render("display6") + $r.render("display7") + $r.render("display8") + $r.render("display9") + #end +</div> \ No newline at end of file -- GitLab