From cd96b388e3a2a3b6e27a52a87bc3031f51383073 Mon Sep 17 00:00:00 2001
From: fkiefer <none@none>
Date: Wed, 1 Feb 2017 17:39:55 +0100
Subject: [PATCH] OO-2483 Add error handling to transcoding and admin GUI

---
 .../olat/modules/video/VideoTranscoding.java  |   3 +
 .../video/ui/TranscodingQueueTableModel.java  |   6 +-
 .../video/ui/TranscodingQueueTableRow.java    |  27 +-
 .../olat/modules/video/ui/TranscodingRow.java |  18 +-
 .../video/ui/TranscodingTableModel.java       |   2 +
 .../video/ui/VideoAdminController.java        |  15 +-
 .../video/ui/VideoAdminErrorController.java   | 259 ++++++++++++++++++
 .../video/ui/VideoAdminListController.java    |  45 ++-
 .../ui/VideoAdminTranscodingController.java   |  18 +-
 .../video/ui/VideoDisplayController.java      |  10 +
 .../ui/VideoQualityTableFormController.java   |  15 +-
 .../modules/video/ui/_content/video_run.html  |   6 +-
 12 files changed, 391 insertions(+), 33 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/video/ui/VideoAdminErrorController.java

diff --git a/src/main/java/org/olat/modules/video/VideoTranscoding.java b/src/main/java/org/olat/modules/video/VideoTranscoding.java
index 42fe4e41c1f..624df6701a5 100644
--- a/src/main/java/org/olat/modules/video/VideoTranscoding.java
+++ b/src/main/java/org/olat/modules/video/VideoTranscoding.java
@@ -34,6 +34,9 @@ import org.olat.resource.OLATResource;
 public interface VideoTranscoding extends CreateInfo {
 	public static final int TRANSCODING_STATUS_WAITING = -1;
 	public static final int TRANSCODING_STATUS_DONE = 100;
+	public static final int TRANSCODING_STATUS_INEFFICIENT = -2;
+	public static final int TRANSCODING_STATUS_ERROR = -3;
+	public static final int TRANSCODING_STATUS_TIMEOUT = -4;
 	public static final String FORMAT_MP4 = "mp4";
 	public static final String TRANSCODER_LOCAL = "Local HandBrakeCLI";
 	
diff --git a/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableModel.java b/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableModel.java
index 47660b462a9..d2f8cabbdf0 100644
--- a/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableModel.java
+++ b/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableModel.java
@@ -58,6 +58,8 @@ public class TranscodingQueueTableModel extends DefaultFlexiTableDataModel<Trans
 			case size: return video.getSize();
 			case format: return video.getFormat();
 			case delete: return video.getDeleteLink();
+			case retranscode: return video.getRetranscodeLink();
+			case failureReason: return video.getFailureReason();
 			default: return "";
 		}
 	}
@@ -70,7 +72,9 @@ public class TranscodingQueueTableModel extends DefaultFlexiTableDataModel<Trans
 		dimension("quality.table.header.dimension"),
 		size("quality.table.header.size"),
 		format("quality.table.header.format"),
-		delete("quality.table.header.delete");
+		delete("quality.table.header.delete"),
+		retranscode("queue.table.header.retranscode"),
+		failureReason("queue.table.failure.reason");
 
 		private final String i18nKey;
 
diff --git a/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableRow.java b/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableRow.java
index 5759f99cb15..aa4b1d5ff4e 100644
--- a/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableRow.java
+++ b/src/main/java/org/olat/modules/video/ui/TranscodingQueueTableRow.java
@@ -31,7 +31,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
  */
 public class TranscodingQueueTableRow {
 
-	private String resid;
+	private FormLink resid;
 	private String displayname;
 	private FormLink creator;
 	private Date creationDate;
@@ -39,6 +39,8 @@ public class TranscodingQueueTableRow {
 	private String size;
 	private String format;
 	private FormLink deleteLink;
+	private FormLink retranscodeLink;
+	private String failureReason;
 
 	protected FormUIFactory uifactory = FormUIFactory.getInstance();
 
@@ -53,7 +55,7 @@ public class TranscodingQueueTableRow {
 	 * @param format the format
 	 * @param deleteLink FormLink for delete row and corresponding Videodata or null if no deleteLink should be shown
 	 */
-	public TranscodingQueueTableRow(String resid, String displayname, Date creationDate, FormLink resolution, String dimension, String size, String format, FormLink deleteLink) {
+	public TranscodingQueueTableRow(FormLink resid, String displayname, Date creationDate, FormLink resolution, String dimension, String size, String format, FormLink deleteLink) {
 		this.resid = resid;
 		this.displayname = displayname;
 		this.creator = resolution;
@@ -65,6 +67,15 @@ public class TranscodingQueueTableRow {
 		if(deleteLink != null) this.deleteLink = deleteLink;
 	}
 	
+
+	public String getFailureReason() {
+		return failureReason;
+	}
+
+	public void setFailureReason(String failureReason) {
+		this.failureReason = failureReason;
+	}
+
 	public Date getCreationDate(){
 		return creationDate;
 	}
@@ -81,11 +92,11 @@ public class TranscodingQueueTableRow {
 		this.displayname = displayname;
 	}
 	
-	public String getResid(){
+	public FormLink getResid(){
 		return resid;
 	}
 	
-	public void setResid(String resid){
+	public void setResid(FormLink resid){
 		this.resid = resid;
 	}
 	
@@ -128,5 +139,13 @@ public class TranscodingQueueTableRow {
 	public void setDeleteLink(FormLink deleteLink) {
 		this.deleteLink = deleteLink;
 	}
+
+	public FormLink getRetranscodeLink() {
+		return retranscodeLink;
+	}
+
+	public void setRetranscodeLink(FormLink retranscodeLink) {
+		this.retranscodeLink = retranscodeLink;
+	}	
 }
 
diff --git a/src/main/java/org/olat/modules/video/ui/TranscodingRow.java b/src/main/java/org/olat/modules/video/ui/TranscodingRow.java
index bd81b47ea37..af565405fab 100644
--- a/src/main/java/org/olat/modules/video/ui/TranscodingRow.java
+++ b/src/main/java/org/olat/modules/video/ui/TranscodingRow.java
@@ -29,16 +29,18 @@ public class TranscodingRow {
 	private int resolution;
 	private int sumVideos;
 	private int missingTranscodings;
+	private int failedTranscodings;
 	private int numberTranscodings;
 	private boolean allTranscoded;
 
-	public TranscodingRow(int resolution, int numberTranscodings, int sumVideos, boolean mayTranscode) {
+	public TranscodingRow(int resolution, int numberTranscodings, int failedTranscodings, int sumVideos, boolean mayTranscode) {
 		super();
 		this.resolution = resolution;
 		this.numberTranscodings = numberTranscodings;
 		this.sumVideos = sumVideos;
-		this.missingTranscodings = sumVideos - numberTranscodings;
-		this.allTranscoded = numberTranscodings < sumVideos && mayTranscode;		
+		this.missingTranscodings = sumVideos - numberTranscodings - failedTranscodings;
+		this.failedTranscodings = failedTranscodings;
+		this.allTranscoded = numberTranscodings + failedTranscodings < sumVideos && mayTranscode;		
 	}
 
 	
@@ -74,9 +76,17 @@ public class TranscodingRow {
 	public void setNumberTranscodings(int numberTranscodings) {
 		this.numberTranscodings = numberTranscodings;
 	}
+	
+	public int getFailedTranscodings() {
+		return failedTranscodings;
+	}
+
+	public void setFailedTranscodings(int failedTranscodings) {
+		this.failedTranscodings = failedTranscodings;
+	}
 
 	public int getMissingTranscodings() {
-		return missingTranscodings;
+		return missingTranscodings >= 0 ? missingTranscodings : 0;
 	}
 
 	public void setMissingTranscodings(int missingTranscodings) {
diff --git a/src/main/java/org/olat/modules/video/ui/TranscodingTableModel.java b/src/main/java/org/olat/modules/video/ui/TranscodingTableModel.java
index 81a6aadf20a..d5534291b93 100644
--- a/src/main/java/org/olat/modules/video/ui/TranscodingTableModel.java
+++ b/src/main/java/org/olat/modules/video/ui/TranscodingTableModel.java
@@ -52,6 +52,7 @@ public class TranscodingTableModel extends DefaultFlexiTableDataModel<Transcodin
 			case resolutions: return translator.translate("quality.resolution." + resolution.getResolution());
 			case sumVideos: return resolution.getSumVideos();
 			case numberTranscodings: return resolution.getNumberTranscodings();
+			case failedTranscodings: return resolution.getFailedTranscodings();
 			case missingTranscodings: return resolution.getMissingTranscodings();
 			case transcode: return resolution.isAllTranscoded();
 			case delete: return resolution.getNumberTranscodings() > 0;
@@ -63,6 +64,7 @@ public class TranscodingTableModel extends DefaultFlexiTableDataModel<Transcodin
 		resolutions("quality.table.header.resolution"),
 		sumVideos("sum.video"),
 		numberTranscodings("number.transcodings"),
+		failedTranscodings("number.transcodings.failed"),
 		missingTranscodings("missing.transcodings"),
 		transcode("quality.transcode"),
 		delete("quality.delete");
diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminController.java
index 8060dadc148..1d2563114a1 100644
--- a/src/main/java/org/olat/modules/video/ui/VideoAdminController.java
+++ b/src/main/java/org/olat/modules/video/ui/VideoAdminController.java
@@ -40,12 +40,13 @@ public class VideoAdminController extends BasicController  {
 
 	
 	private final SegmentViewComponent segmentView;
-	private Link adminSetLink, adminListLink, adminTranscodingLink;
+	private Link adminSetLink, adminListLink, adminTranscodingLink, adminErrorLink;
 	private VelocityContainer mainVC;
 	
 	private VideoAdminSetController adminSetController;
 	private VideoAdminListController adminListController;
 	private VideoAdminTranscodingController adminTranscodingController;
+	private VideoAdminErrorController adminErrorController;
 
 	public VideoAdminController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
@@ -57,6 +58,8 @@ public class VideoAdminController extends BasicController  {
 		segmentView.addSegment(adminSetLink, true);
 		adminListLink = LinkFactory.createLink("tab.admin.list", mainVC, this);
 		segmentView.addSegment(adminListLink, false);
+		adminErrorLink = LinkFactory.createLink("tab.admin.error", mainVC, this);
+		segmentView.addSegment(adminErrorLink, false);
 		adminTranscodingLink = LinkFactory.createLink("tab.admin.transcoding", mainVC, this);
 		segmentView.addSegment(adminTranscodingLink, false);
 		
@@ -80,11 +83,21 @@ public class VideoAdminController extends BasicController  {
 					doOpenAdminList(ureq);
 				} else if (clickedLink == adminTranscodingLink){
 					doOpenTranscodingAdmin(ureq);
+				} else if (clickedLink == adminErrorLink) {
+					doOpenErrorAdmin(ureq);
 				}
 			}
 		}
 	}
 	
+	private void doOpenErrorAdmin(UserRequest ureq) {
+		if(adminErrorController == null) {
+			adminErrorController = new VideoAdminErrorController(ureq, getWindowControl());
+			listenTo(adminErrorController);
+		}
+		mainVC.put("segmentCmp", adminErrorController.getInitialComponent());
+	}
+	
 	private void doOpenAdminConfig(UserRequest ureq) {
 		if(adminSetController == null) {
 			adminSetController = new VideoAdminSetController(ureq, getWindowControl());
diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminErrorController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminErrorController.java
new file mode 100644
index 00000000000..533100152d5
--- /dev/null
+++ b/src/main/java/org/olat/modules/video/ui/VideoAdminErrorController.java
@@ -0,0 +1,259 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.video.ui;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.NewControllerFactory;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.id.Identity;
+import org.olat.course.CorruptedCourseException;
+import org.olat.modules.video.VideoManager;
+import org.olat.modules.video.VideoTranscoding;
+import org.olat.modules.video.ui.TranscodingQueueTableModel.TranscodingQueueTableCols;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.olat.repository.handlers.RepositoryHandler;
+import org.olat.repository.handlers.RepositoryHandlerFactory;
+import org.olat.user.HomePageConfig;
+import org.olat.user.HomePageDisplayController;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial Date: 31.01.2017
+ * The Class VideoAdminErrorController.
+ * @author fkiefer fabian.kiefer@frentix.com
+ * 
+ * shows a list of all FAILED transcoding orders 
+ */
+public class VideoAdminErrorController extends FormBasicController {
+	
+	private TranscodingQueueTableModel tableModel;
+	private FlexiTableElement tableEl;
+	private FormItemContainer formLayout;
+	private FormLink refreshButton;
+	private CloseableModalController closeableModalController;
+	private HomePageDisplayController homePageDisplayController;
+
+	
+	private int counter = 0;
+
+	@Autowired
+	private VideoManager videoManager;
+	@Autowired
+	private UserManager userManager;
+	@Autowired 
+	private RepositoryService repositoryService;
+	@Autowired
+	private BaseSecurity baseSecurity;   
+	@Autowired
+	private RepositoryHandlerFactory repositoryHandlerFactory;
+
+	
+	public VideoAdminErrorController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl,"transcoding_queue");
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.resid));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.displayname));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.failureReason));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.creator));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.creationDate));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.dimension));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.format));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.retranscode));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingQueueTableCols.delete));
+		tableModel = new TranscodingQueueTableModel(columnsModel, getTranslator());
+		
+		initForm(ureq);
+	}
+	
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		this.formLayout = formLayout;
+		setFormTitle("number.transcodings");
+		setFormDescription("number.transcodings");
+		setFormContextHelp("Portfolio template: Administration and editing#configuration");
+		
+		initTable();
+	}
+	
+	private void initTable () {
+		List<VideoTranscoding> videoTranscodings = videoManager.getFailedVideoTranscodings();
+		List<TranscodingQueueTableRow> rows = new ArrayList<>();
+		
+		for (VideoTranscoding videoTranscoding : videoTranscodings) {
+
+			String title = videoManager.getDisplayTitleForResolution(videoTranscoding.getResolution(), getTranslator());
+			String resid = String.valueOf(videoTranscoding.getVideoResource().getResourceableId());
+			FormLink resourceLink = uifactory.addFormLink("res_" + counter++, "viewResource", resid, resid, flc, Link.LINK + Link.NONTRANSLATED);
+			resourceLink.setUserObject(videoTranscoding);
+			FormLink deleteLink = uifactory.addFormLink("del_" + counter++, "deleteQuality", "quality.delete", "quality.delete", flc, Link.LINK);
+			deleteLink.setUserObject(videoTranscoding);
+			deleteLink.setIconLeftCSS("o_icon o_icon_delete_item o_icon-fw");
+			FormLink retranscodeLink = uifactory.addFormLink("trans_" + counter++, "retranscode", "queue.retranscode", "queue.retranscode", flc, Link.LINK);
+			retranscodeLink.setUserObject(videoTranscoding);
+			retranscodeLink.setIconLeftCSS("o_icon o_icon_refresh o_icon-fw");
+
+			String failureReason = "";
+			if (videoTranscoding.getStatus() == VideoTranscoding.TRANSCODING_STATUS_INEFFICIENT) {
+				failureReason = translate("transcoding.inefficient");
+			} else if (videoTranscoding.getStatus() == VideoTranscoding.TRANSCODING_STATUS_ERROR) {
+				failureReason = translate("transcoding.error");
+			} else if (videoTranscoding.getStatus() == VideoTranscoding.TRANSCODING_STATUS_TIMEOUT) {
+				failureReason = translate("transcoding.timeout");
+			} 
+
+			RepositoryEntry videoRe = repositoryService.loadByResourceKey(videoTranscoding.getVideoResource().getKey());
+			if (videoRe == null) continue;
+			String displayname = videoRe.getDisplayname();
+			String initialAuthor = videoRe.getInitialAuthor();
+			String fullName = userManager.getUserDisplayName(initialAuthor);
+			FormLink authorLink = uifactory.addFormLink("author_" + counter++, "viewAuthor",
+					fullName, fullName, flc, Link.LINK + Link.NONTRANSLATED);
+			authorLink.setUserObject(initialAuthor);
+			Date creationDate = videoTranscoding.getCreationDate();
+			TranscodingQueueTableRow transcodingrow = new TranscodingQueueTableRow(resourceLink, displayname, creationDate, authorLink,
+					title, null, videoTranscoding.getFormat(), deleteLink);
+			transcodingrow.setFailureReason(failureReason);
+			transcodingrow.setRetranscodeLink(retranscodeLink);
+			
+			rows.add(transcodingrow);
+		}
+		tableModel.setObjects(rows);
+		
+		if (formLayout.hasFormComponent(tableEl)){
+			formLayout.remove(tableEl);
+		}
+		if (formLayout.hasFormComponent(refreshButton)){
+			formLayout.remove(refreshButton);
+		}
+		
+		tableEl = uifactory.addTableElement(getWindowControl(), "queue", tableModel, getTranslator(), formLayout);
+		tableEl.setCustomizeColumns(false);
+		tableEl.setNumOfRowsEnabled(false);
+		
+		refreshButton = uifactory.addFormLink("button.refresh", formLayout,Link.BUTTON);
+		refreshButton.setIconLeftCSS("o_icon o_icon_refresh o_icon-fw");
+	}
+
+	@Override
+	public void event(UserRequest ureq, Component source, Event event) {
+		super.event(ureq, source, event);
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if (source == homePageDisplayController || source == closeableModalController){
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source instanceof FormLink && ((FormLink) source).getCmd().equals("deleteQuality")) {
+			FormLink link = (FormLink) source;
+			VideoTranscoding videoTranscoding = (VideoTranscoding) link.getUserObject();
+			videoManager.deleteVideoTranscoding(videoTranscoding);
+		} else if (source instanceof FormLink && ((FormLink) source).getCmd().equals("viewAuthor")) {
+			showUserInfo(ureq, baseSecurity.findIdentityByName((String) source.getUserObject()));
+		} else if (source instanceof FormLink && ((FormLink) source).getCmd().equals("retranscode")) {
+			FormLink link = (FormLink) source;
+			VideoTranscoding videoTranscoding = (VideoTranscoding) link.getUserObject();
+			videoManager.retranscodeFailedVideoTranscoding(videoTranscoding);
+		} else if (source instanceof FormLink && ((FormLink) source).getCmd().equals("viewResource")) {
+			FormLink link = (FormLink) source;
+			VideoTranscoding videoTranscoding = (VideoTranscoding) link.getUserObject();
+			launch(ureq, videoTranscoding);
+		}
+		initTable();
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		
+	}
+
+	@Override
+	protected void doDispose() {
+		
+	}
+	
+	protected void launch(UserRequest ureq, VideoTranscoding videoTranscoding) {
+		RepositoryEntry videoRe = repositoryService.loadByResourceKey(videoTranscoding.getVideoResource().getKey());
+		try {
+			RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler("FileResource.VIDEO");
+			if(handler != null) {
+
+				String businessPath = "[RepositoryEntry:" + videoRe.getKey() + "]";
+				if(!NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl())) {
+					tableEl.reloadData();
+				}
+			}
+		} catch (CorruptedCourseException e) {
+			logError("Course corrupted: " + videoRe.getKey(), e);
+			showError("cif.error.corrupted");
+		}
+	}
+	
+	/**
+	 * Method to open the users visiting card in a new tab. Public to call it also from the parent controller
+	 * @param ureq
+	 */
+	public void showUserInfo(UserRequest ureq, Identity userID) {
+		
+		homePageDisplayController = new HomePageDisplayController(ureq, getWindowControl(), userID, new HomePageConfig());
+		
+		closeableModalController = new CloseableModalController(getWindowControl(), translate("close"), 
+				homePageDisplayController.getInitialComponent(), true, translate("video.contact"));
+		listenTo(closeableModalController);
+		
+		closeableModalController.activate();
+	}
+	
+	private void cleanUp(){
+		closeableModalController.deactivate();
+		removeAsListenerAndDispose(closeableModalController);
+		closeableModalController = null;
+		homePageDisplayController = null;
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminListController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminListController.java
index dbe8a99d769..2de3edb35ed 100644
--- a/src/main/java/org/olat/modules/video/ui/VideoAdminListController.java
+++ b/src/main/java/org/olat/modules/video/ui/VideoAdminListController.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
+import org.olat.NewControllerFactory;
 import org.olat.basesecurity.BaseSecurity;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
@@ -42,11 +43,14 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.id.Identity;
 import org.olat.core.util.Formatter;
+import org.olat.course.CorruptedCourseException;
 import org.olat.modules.video.VideoManager;
 import org.olat.modules.video.VideoTranscoding;
 import org.olat.modules.video.ui.TranscodingQueueTableModel.TranscodingQueueTableCols;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryService;
+import org.olat.repository.handlers.RepositoryHandler;
+import org.olat.repository.handlers.RepositoryHandlerFactory;
 import org.olat.user.HomePageConfig;
 import org.olat.user.HomePageDisplayController;
 import org.olat.user.UserManager;
@@ -80,6 +84,8 @@ public class VideoAdminListController extends FormBasicController {
 	private RepositoryService repositoryService;
 	@Autowired
 	private BaseSecurity baseSecurity;   
+	@Autowired
+	protected RepositoryHandlerFactory repositoryHandlerFactory;
 
 	public VideoAdminListController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl,"transcoding_queue");
@@ -112,18 +118,15 @@ public class VideoAdminListController extends FormBasicController {
 		List<VideoTranscoding> videoTranscodings = videoManager.getVideoTranscodingsPendingAndInProgress();
 		List<TranscodingQueueTableRow> rows = new ArrayList<>();
 		
-		for(VideoTranscoding videoTranscoding:videoTranscodings){
+		for (VideoTranscoding videoTranscoding : videoTranscodings) {
 			String title = videoManager.getDisplayTitleForResolution(videoTranscoding.getResolution(), getTranslator());
 			String resid = String.valueOf(videoTranscoding.getVideoResource().getResourceableId());
-			FormLink previewVersionLink = uifactory.addFormLink("res_" + counter++, "viewQuality", resid, resid, flc, Link.LINK + Link.NONTRANSLATED);
+			FormLink resourceLink = uifactory.addFormLink("res_" + counter++, "viewResource", resid, resid, flc, Link.LINK + Link.NONTRANSLATED);
+			resourceLink.setUserObject(videoTranscoding);
 			FormLink deleteLink = uifactory.addFormLink("del_" + counter++, "deleteQuality", "quality.delete", "quality.delete", flc, Link.LINK);
 			deleteLink.setUserObject(videoTranscoding);
-			deleteLink.setIconLeftCSS("o_icon o_icon_delete_item o_icon-fw");
-			
-			previewVersionLink.setUserObject(videoTranscoding);
-			if (videoTranscoding.getStatus() < VideoTranscoding.TRANSCODING_STATUS_DONE) {
-				previewVersionLink.setEnabled(false);
-			}
+			deleteLink.setIconLeftCSS("o_icon o_icon_delete_item o_icon-fw");			
+
 			String fileSize = "";
 			if (videoTranscoding.getSize() != 0) {
 				fileSize = Formatter.formatBytes(videoTranscoding.getSize());
@@ -133,6 +136,7 @@ public class VideoAdminListController extends FormBasicController {
 				fileSize = translate("transcoding.processing") + ": " + videoTranscoding.getStatus() + "%";					
 			}
 			RepositoryEntry videoRe = repositoryService.loadByResourceKey(videoTranscoding.getVideoResource().getKey());
+			if (videoRe == null) continue;
 			String displayname = videoRe.getDisplayname();
 			String initialAuthor = videoRe.getInitialAuthor();
 			String fullName = userManager.getUserDisplayName(initialAuthor);
@@ -140,7 +144,7 @@ public class VideoAdminListController extends FormBasicController {
 					fullName, fullName, flc, Link.LINK + Link.NONTRANSLATED);
 			authorLink.setUserObject(initialAuthor);
 			Date creationDate = videoTranscoding.getCreationDate();
-			rows.add(new TranscodingQueueTableRow(resid, displayname, creationDate, authorLink, title, fileSize, videoTranscoding.getFormat(), deleteLink));
+			rows.add(new TranscodingQueueTableRow(resourceLink, displayname, creationDate, authorLink, title, fileSize, videoTranscoding.getFormat(), deleteLink));
 		}
 		tableModel.setObjects(rows);
 		
@@ -158,8 +162,6 @@ public class VideoAdminListController extends FormBasicController {
 		refreshButton = uifactory.addFormLink("button.refresh", formLayout,Link.BUTTON);
 		refreshButton.setIconLeftCSS("o_icon o_icon_refresh o_icon-fw");
 
-
-
 	}
 
 	@Override
@@ -183,6 +185,10 @@ public class VideoAdminListController extends FormBasicController {
 			videoManager.deleteVideoTranscoding(videoTranscoding);
 		} else if (source instanceof FormLink && ((FormLink) source).getCmd().equals("viewAuthor")) {
 			showUserInfo(ureq, baseSecurity.findIdentityByName((String) source.getUserObject()));
+		} else if (source instanceof FormLink && ((FormLink) source).getCmd().equals("viewResource")) {
+			FormLink link = (FormLink) source;
+			VideoTranscoding videoTranscoding = (VideoTranscoding) link.getUserObject();
+			launch(ureq, videoTranscoding);
 		}
 		initTable();
 	};
@@ -197,6 +203,23 @@ public class VideoAdminListController extends FormBasicController {
 		
 	}
 	
+	private void launch(UserRequest ureq, VideoTranscoding videoTranscoding) {
+		RepositoryEntry videoRe = repositoryService.loadByResourceKey(videoTranscoding.getVideoResource().getKey());
+		try {
+			RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler("FileResource.VIDEO");
+			if(handler != null) {
+
+				String businessPath = "[RepositoryEntry:" + videoRe.getKey() + "]";
+				if(!NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl())) {
+					tableEl.reloadData();
+				}
+			}
+		} catch (CorruptedCourseException e) {
+			logError("Course corrupted: " + videoRe.getKey(), e);
+			showError("cif.error.corrupted");
+		}
+	}
+	
 	/**
 	 * Method to open the users visiting card in a new tab. Public to call it also from the parent controller
 	 * @param ureq
diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java
index f0d8d4f29da..cc67f036cb7 100644
--- a/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java
+++ b/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java
@@ -91,6 +91,7 @@ public class VideoAdminTranscodingController extends FormBasicController {
 		transcodingModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingCols.resolutions));
 		transcodingModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingCols.sumVideos));
 		transcodingModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingCols.numberTranscodings));
+		transcodingModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingCols.failedTranscodings));
 		transcodingModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingCols.missingTranscodings));
 		transcodingModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TranscodingCols.transcode, "quality.transcode", 
 				new BooleanCellRenderer(new StaticFlexiCellRenderer(translate("quality.transcode"), "quality.transcode"), null)));
@@ -122,17 +123,24 @@ public class VideoAdminTranscodingController extends FormBasicController {
 		List<TranscodingRow> resolutions = new ArrayList<>();
 		// Hardcoded same as VideoAdminSetController
 		int[] fixresolution = { 2160, 1080, 720, 480, 360, 240 };
-		Map<Integer, Integer> resCount = new HashMap<>();
-		for (TranscodingCount transcodingCount : videoManager.getAllVideoTranscodingsCount()) {
-			resCount.put(transcodingCount.getResolution(), transcodingCount.getCount());
+		Map<Integer, Integer> successCount = new HashMap<>();
+		int beginErrorCode = VideoTranscoding.TRANSCODING_STATUS_INEFFICIENT;
+		for (TranscodingCount transcodingCount : videoManager.getAllVideoTranscodingsCountSuccess(beginErrorCode)) {
+			successCount.put(transcodingCount.getResolution(), transcodingCount.getCount());
+		}
+		Map<Integer, Integer> failCount = new HashMap<>();
+		for (TranscodingCount transcodingCount : videoManager.getAllVideoTranscodingsCountFails(beginErrorCode)) {
+			failCount.put(transcodingCount.getResolution(), transcodingCount.getCount());
 		}
 		for (int i = 0; i < fixresolution.length; i++) {
 			int counter = 0;
 			for (OLATResource videoResource : nativeResolutions.keySet()) {
 				if (nativeResolutions.get(videoResource) >= fixresolution[i]) counter++;
 			}
-			int rescount = resCount.get(fixresolution[i]) != null ? resCount.get(fixresolution[i]) : 0;
-			resolutions.add(new TranscodingRow(fixresolution[i], rescount, counter, mayTranscode(fixresolution[i])));
+			int Scount = successCount.get(fixresolution[i]) != null ? successCount.get(fixresolution[i]) : 0;
+			int Fcount = failCount.get(fixresolution[i]) != null ? failCount.get(fixresolution[i]) : 0;
+			TranscodingRow transcodingRow = new TranscodingRow(fixresolution[i], Scount, Fcount, counter, mayTranscode(fixresolution[i])); 
+			resolutions.add(transcodingRow);
 		}
 		if (resolutions != null){
 			tableModel.setObjects(resolutions);
diff --git a/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java b/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java
index cd3abd12cb5..5c7b7e797ba 100644
--- a/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java
+++ b/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java
@@ -28,6 +28,7 @@ import org.olat.core.commons.services.commentAndRating.CommentAndRatingDefaultSe
 import org.olat.core.commons.services.commentAndRating.CommentAndRatingSecurityCallback;
 import org.olat.core.commons.services.commentAndRating.ReadOnlyCommentsSecurityCallback;
 import org.olat.core.commons.services.commentAndRating.ui.UserCommentsAndRatingsController;
+import org.olat.core.commons.services.image.Size;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSComponent;
@@ -194,6 +195,11 @@ public class VideoDisplayController extends BasicController {
 		mainVC.contextPut("authors", (StringHelper.containsNonWhitespace(authors) ? authors : null));
 
 		if(video != null) {
+			// get resolution of master video resource 
+			Size masterResolution = videoManager.getVideoResolutionFromOLATResource(entry.getOlatResource());
+			String masterTitle = videoManager.getDisplayTitleForResolution(masterResolution.getHeight(), getTranslator());
+			String masterSize = " (" + Formatter.formatBytes(videoManager.getVideoMetadata(entry.getOlatResource()).getSize()) + ")";
+			boolean addMaster = true;
 			// Mapper for Video
 			String masterMapperId = "master-" + entry.getOlatResource().getResourceableId();
 			String masterUrl = registerCacheableMapper(ureq, masterMapperId, new VideoMediaMapper(videoManager.getMasterContainer(entry.getOlatResource())));
@@ -213,6 +219,8 @@ public class VideoDisplayController extends BasicController {
 			for (VideoTranscoding videoTranscoding : videos) {
 				if (videoTranscoding.getStatus() == VideoTranscoding.TRANSCODING_STATUS_DONE) {
 					readyToPlayVideos.add(videoTranscoding);
+					// Check if at least one has equal height, else use master as resource
+					addMaster &= videoTranscoding.getHeight() < masterResolution.getHeight();
 					// Use the users preferred resolution or the next higher resolution
 					if (videoTranscoding.getResolution() >= userPreferredResolution.intValue()) {
 						preferredAvailableResolution = readyToPlayVideos.size() - 1;
@@ -222,6 +230,8 @@ public class VideoDisplayController extends BasicController {
 					displayTitles.add(title);
 				}
 			}
+			mainVC.contextPut("addMaster", addMaster);			
+			mainVC.contextPut("masterTitle", masterTitle + masterSize);
 			mainVC.contextPut("videos", readyToPlayVideos);
 			mainVC.contextPut("displayTitles", displayTitles);
 			mainVC.contextPut("useSourceChooser", Boolean.valueOf(readyToPlayVideos.size() > 1));
diff --git a/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java b/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java
index eff5876ed81..1cf1a788ff5 100644
--- a/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java
+++ b/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java
@@ -124,13 +124,20 @@ public class VideoQualityTableFormController extends FormBasicController {
 			int height = videoTranscoding.getHeight();
 			String dimension = width +"x"+ height;
 			String fileSize = "";
-			if (videoTranscoding.getSize() != 0) {
+			int status = videoTranscoding.getStatus();
+			if (videoTranscoding.getSize() != 0 && status > -1) {
 				fileSize = Formatter.formatBytes(videoTranscoding.getSize());
-			} else if (videoTranscoding.getStatus() == VideoTranscoding.TRANSCODING_STATUS_WAITING) {
+			} else if (status == VideoTranscoding.TRANSCODING_STATUS_WAITING) {
 				fileSize = translate("transcoding.waiting");
-			} else if (videoTranscoding.getStatus() <= VideoTranscoding.TRANSCODING_STATUS_DONE){
+			} else if (status <= VideoTranscoding.TRANSCODING_STATUS_DONE && status > -1){
 				fileSize = translate("transcoding.processing") + ": " + videoTranscoding.getStatus() + "%";					
-			}
+			} else if (status == VideoTranscoding.TRANSCODING_STATUS_INEFFICIENT) {
+				fileSize = translate("transcoding.inefficient");
+			} else if (status == VideoTranscoding.TRANSCODING_STATUS_ERROR) {
+				fileSize = translate("transcoding.error");
+			} else if (status == VideoTranscoding.TRANSCODING_STATUS_TIMEOUT) {
+				fileSize = translate("transcoding.timeout");
+			} 
 			rows.add(new QualityTableRow(previewVersionLink, dimension,  fileSize, videoTranscoding.getFormat(), deleteLink));
 		}
 		List<Integer> missingResolutions = videoManager.getMissingTranscodings(videoResource);
diff --git a/src/main/java/org/olat/modules/video/ui/_content/video_run.html b/src/main/java/org/olat/modules/video/ui/_content/video_run.html
index 997c835ea33..cf97e03f476 100644
--- a/src/main/java/org/olat/modules/video/ui/_content/video_run.html
+++ b/src/main/java/org/olat/modules/video/ui/_content/video_run.html
@@ -1,14 +1,14 @@
 <div class="o_video_run o_block_large_bottom clearfix">
 	<div class="olatFlashMovieViewer">
 		<video id="$r.getId("o_vid")" width="$width" height="$height" #if($usePoster) poster="$masterUrl/poster.jpg" #end controls #if(!$hasChapters) preload="none" #end oncontextmenu="return false;" #if( $autoplay ) autoplay #end class="o_video">			
+	    	#if ($videos.size() == 0 || $addMaster)
+		    	<source type="video/mp4" src="$masterUrl/video.mp4" title="$masterTitle"/>
+		    #end
 	    	#foreach($video in $videos)
 	    		#set($position = $velocityCount - 1)
 	    		<source type="video/mp4" src="$transcodedUrl/${video.getResolution()}video.mp4" title="$displayTitles.get($position) ($r.formatBytes(${video.getSize()}))" />
 	    	#end	    	
 	    	## Use master video file if not optimized video is found
-	    	#if ($videos.size() == 0)
-		    	<source type="video/mp4" src="$masterUrl/video.mp4" />
-		    #end
 	    	
 	  		#if( $trackfiles )
 		  		#foreach( $track in $trackfiles.keySet())
-- 
GitLab