diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 311fe0a0333fd8323ceb65bfff78880a39bf0ebc..ba3ce608de70528ee27aaf32fd57c663bb9a2c18 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -6,7 +6,7 @@ import { useRouter } from "next/router";
 import type { GetHomepageResponse, featured, int } from "@/videoag/api/types";
 import { useApi } from "@/videoag/api/ApiProvider";
 import { useUserContext } from "@/videoag/authentication/UserDataProvider";
-import { VideoCard } from "@/videoag/course/VideoCard";
+import { LectureCard } from "@/videoag/course/Lecture";
 import { LectureLiveLabel } from "@/videoag/course/LiveLabel";
 import { ErrorComponent, showError, showWarningToast } from "@/videoag/error/ErrorDisplay";
 import { FallbackErrorBoundary } from "@/videoag/error/FallbackErrorBoundary";
@@ -234,7 +234,7 @@ function RecentUploads({ homepageData }: { homepageData: ResourceType<GetHomepag
                 let course_data = data.course_context[lecture.course_id];
                 return (
                     <li className="list-group-item" key={index}>
-                        <VideoCard lecture={lecture} course={course_data} />{" "}
+                        <LectureCard lecture={lecture} course={course_data} />{" "}
                     </li>
                 );
             })}
diff --git a/src/styles/globals.scss b/src/styles/globals.scss
index cb601054e47cfa894ed25a1a2e59653dd66385af..a87f1c888b1382564197a669d60af866d0875ceb 100644
--- a/src/styles/globals.scss
+++ b/src/styles/globals.scss
@@ -164,6 +164,36 @@ $link-decoration: none;
 	transition: none !important;
 }
 
+
+.play-button {
+    // Parents needs to have position: relative
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    text-align: center;
+    font-size: 200%;
+    line-height: 400%;
+}
+
+.centered-overlay-container {
+    // Parents needs to have position: relative
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.center-background-img {
+    background-size: contain;
+    background-repeat: no-repeat;
+    background-position: 50% 50%;
+}
+
 .vjs-rwth.video-js {
 
 	.vjs-control-bar {
diff --git a/src/styles/legacy_style.css b/src/styles/legacy_style.css
index 2990a710eb0f695926063528211e52ac2fc31e44..11204d01e9b5697faf49b242d7fb0b65b810bb11 100644
--- a/src/styles/legacy_style.css
+++ b/src/styles/legacy_style.css
@@ -1,25 +1,4 @@
 
-.playpreviewbtn {
-	text-decoration: none;
-	width: 100%;
-	left: 0px;
-	color: lightgrey;
-	position: absolute;
-	font-size: 3em;
-	text-align: center;
-	line-height: 130px;
-	text-shadow: 0 0 2px black;
-}
-
-.thumbnailimg {
-	height: 130px;
-	position: relative;
-	margin-bottom: 10px;
-	background-size: contain;
-	background-repeat: no-repeat;
-	background-position: 50% 50%;
-}
-
 @media (min-width: 767px) {
 	.list-group-item-condensed { padding-top: 2px !important; padding-bottom: 2px !important; }
 }
diff --git a/src/videoag/authentication/ViewPermissions.tsx b/src/videoag/authentication/ViewPermissions.tsx
index 5d23d6faf5eb07205b4fe2be9881d5c1e9a70cda..fa574e6df39223d3213a738176410035be6000c0 100644
--- a/src/videoag/authentication/ViewPermissions.tsx
+++ b/src/videoag/authentication/ViewPermissions.tsx
@@ -11,7 +11,7 @@ export function AuthenticationMethodIcons({
         );
     }
     return (
-        <div>
+        <span className="d-flex justify-content-end flex-wrap">
             {authentication_methods.map((method) => {
                 let icon = undefined;
                 let description = "";
@@ -58,6 +58,6 @@ export function AuthenticationMethodIcons({
                     </span>
                 );
             })}
-        </div>
+        </span>
     );
 }
diff --git a/src/videoag/course/CourseListing.tsx b/src/videoag/course/CourseListing.tsx
index d7f7e0f598cef584fa2124f86beb7e92d4610b5e..95ab384c08e7f98220b36f1cd20d4bd0c4d0cdbc 100644
--- a/src/videoag/course/CourseListing.tsx
+++ b/src/videoag/course/CourseListing.tsx
@@ -26,8 +26,8 @@ function ListingHeader({ course }: { course: GetCourseResponse }) {
     const { language } = useLanguage();
 
     return (
-        <div className="card mb-3">
-            <div className={`card-body ${course.visible === false ? "bg-danger-subtle" : ""}`}>
+        <div className={`card mb-3 ${course.visible === false ? "bg-danger-subtle" : ""}`}>
+            <div className="card-body">
                 <h5 className="card-title d-flex">
                     <span className="panel-title flex-fill">
                         <EmbeddedOMFieldComponent
diff --git a/src/videoag/course/DownloadManager.tsx b/src/videoag/course/DownloadManager.tsx
index 6968aa7d4112b74daff316a25041bdcf0a94cf6b..bc6a8d2c785a3b1dd6fdcb9e2584e989feb80d99 100644
--- a/src/videoag/course/DownloadManager.tsx
+++ b/src/videoag/course/DownloadManager.tsx
@@ -2,572 +2,517 @@ import { useEffect, useRef, useState } from "react";
 import Modal from "react-bootstrap/Modal";
 import DropdownButton from "react-bootstrap/DropdownButton";
 import DropdownItem from "react-bootstrap/DropdownItem";
-import { sortSourceNames, sortSources, sourceToName } from "./Player";
-import { showWarningToast } from "@/videoag/error/ErrorDisplay";
 
-import type { GetCourseResponse, publish_medium } from "@/videoag/api/types";
+import type { course, publish_medium } from "@/videoag/api/types";
+import { showWarningToast, showError } from "@/videoag/error/ErrorDisplay";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
 import { StylizedText } from "@/videoag/miscellaneous/StylizedText";
 import { filesizeToHuman } from "@/videoag/miscellaneous/Formatting";
+import { showInfoToast } from "@/videoag/miscellaneous/Util";
+
+import { sortSourceNames, sortSources, sourceToName } from "./Player";
+import { sortPublishMediaByQuality, getNamedPublishMedia, getSortedDownloadablePublishMedia, getMediumQualityName } from "./Medium";
 
 interface DownloadStatus {
-    downloaded: number;
-    total: number;
+    downloadedBytes: number;
+    totalBytes: number;
     done: boolean;
-    percent: number;
     error?: string;
 }
 
-// TODO rework. Why is there so much duplicate code? More adjustements for new publish_media needed
-
-async function downloadWithFileSystemAPI(
-    replaceStatus: (id: number, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    course: GetCourseResponse,
-    chosenLectures: any,
-    chosenQualities: any,
-    requestStopDownload: any,
-) {
-    if (!(window as any).showDirectoryPicker) {
-        throw "unsupported";
-    }
-    const saveDir = await (window as any).showDirectoryPicker();
-    if (!saveDir) {
-        return "cancelled";
-    }
+//TODO check authorized
 
-    await saveDir.requestPermission({ mode: "readwrite" });
+//TODO Test ospf and file system api
 
-    for (let lecture of course.lectures!) {
-        if (chosenLectures.current.get(lecture.id) && lecture.publish_media) {
-            try {
-                if (requestStopDownload.current) {
-                    replaceStatus(lecture.id, () => ({
-                        downloaded: 0,
-                        total: 0,
-                        done: false,
-                        percent: 0,
-                        error: "Download cancelled",
-                    }));
-                    continue;
-                }
-                let chosenMedium: publish_medium | undefined = undefined;
-                for (let medium of lecture.publish_media) {
-                    if (sourceToName(medium) === chosenQualities.current.get(lecture.id)) {
-                        chosenMedium = medium;
-                        break;
-                    }
-                }
-                if (!chosenMedium || !chosenMedium.url) continue;
-
-                // TODO file ending mp4. title often empty?
-                let filename = `videoag_${course.id}_${lecture.id}_${chosenMedium.title}.mp4`;
-
-                let fileHandle = await saveDir.getFileHandle(filename, { create: true });
-                let writable = await fileHandle.createWritable();
-                let response = await fetch(chosenMedium.url!);
-                if (response.ok === false)
-                    throw new Error("Download failed, status " + response.status);
-                let reader = response.body!.getReader();
-                let writer = writable.getWriter();
-                let lastUpdate = Date.now();
-                let downloaded = 0;
-                let total = chosenMedium.medium_metadata.file_size;
-                replaceStatus(lecture.id, () => ({
-                    downloaded,
-                    total,
+async function downloadToDirectoryHandle(
+    replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
+    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    failNowOnException: any,
+    requestStopDownload: any,
+    saveDir: any,
+    onFileDownloaded: (fileHandle: any) => Promise<void>,
+): boolean {
+    for (const toDownload of filesToDownload) {
+        try {
+            if (requestStopDownload.current) {
+                replaceStatus(toDownload.lectureId, () => ({
+                    downloadedBytes: 0,
+                    totalBytes: 0,
                     done: false,
-                    percent: 0,
+                    error: "Download cancelled",
                 }));
-                while (true) {
-                    const { done, value } = await reader.read();
-                    if (done || requestStopDownload.current) {
-                        break;
-                    }
-                    await writer.write(value);
-                    downloaded += value!.byteLength;
-                    let now = Date.now();
-                    // after 0.2s, update progress
-                    if (now - lastUpdate > 200) {
-                        replaceStatus(lecture.id, () => ({
-                            downloaded,
-                            total,
-                            done: false,
-                            percent: (downloaded / total) * 100,
-                        }));
-                        lastUpdate = now;
-                    }
+                continue;
+            }
+            let fileHandle = await saveDir.getFileHandle(toDownload.filename, { create: true });
+            let writable = await fileHandle.createWritable();
+            let response = await fetch(toDownload.url);
+            if (response.ok === false)
+                throw new Error("Download failed, status " + response.status);
+            let reader = response.body!.getReader();
+            let writer = writable.getWriter();
+            let lastUpdate = Date.now();
+            let downloadedBytes = 0;
+            let totalBytes = toDownload.fileSize;
+            replaceStatus(toDownload.lectureId, () => ({
+                downloadedBytes,
+                totalBytes,
+                done: false,
+            }));
+            while (true) {
+                const { done, value } = await reader.read();
+                if (done || requestStopDownload.current) {
+                    break;
                 }
-                await writer.close();
-                if (requestStopDownload.current) {
-                    // remove incomplete file
-                    saveDir.removeEntry(filename);
-                    replaceStatus(lecture.id, (old) => ({
-                        ...old!,
-                        error: "Download cancelled",
-                    }));
-                } else {
-                    replaceStatus(lecture.id, () => ({
-                        downloaded,
-                        total,
-                        done: true,
-                        percent: 100,
+                await writer.write(value);
+                downloadedBytes += value!.byteLength;
+                failNowOnException.current = true;
+                let now = Date.now();
+                // after 0.2s, update progress
+                if (now - lastUpdate > 200) {
+                    replaceStatus(toDownload.lectureId, () => ({
+                        downloadedBytes,
+                        totalBytes,
+                        done: false,
                     }));
+                    lastUpdate = now;
                 }
-            } catch (e: any) {
-                replaceStatus(lecture.id, (old) => ({
+            }
+            await writer.close();
+            if (requestStopDownload.current) {
+                // remove incomplete file
+                saveDir.removeEntry(toDownload.filename);
+                replaceStatus(toDownload.lectureId, (old) => ({
                     ...old!,
-                    error: e + "",
+                    error: "Download cancelled",
+                }));
+            } else {
+                await onFileDownloaded(fileHandle);
+                replaceStatus(toDownload.lectureId, () => ({
+                    downloadedBytes,
+                    totalBytes,
+                    done: true,
                 }));
             }
+        } catch (e: any) {
+            replaceStatus(toDownload.lectureId, (old) => ({
+                ...old!,
+                error: e + "",
+            }));
         }
     }
+    return true;
+}
 
-    return "success";
+async function downloadWithFileSystemAPI(
+    replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
+    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    failNowOnException: any,
+    requestStopDownload: any,
+): boolean {
+    if (!(window as any).showDirectoryPicker) {
+        throw new Error("File System API unsupported");
+    }
+    const saveDir = await (window as any).showDirectoryPicker();
+    if (!saveDir) {
+        return;
+    }
+
+    await saveDir.requestPermission({ mode: "readwrite" });
+
+    return downloadToDirectoryHandle(
+        replaceStatus,
+        filesToDownload,
+        failNowOnException,
+        requestStopDownload,
+        saveDir,
+        (fileHandle) => undefined,
+    )
 }
 
 async function downloadWithOPFSAPI(
-    replaceStatus: (id: number, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    course: GetCourseResponse,
-    chosenLectures: any,
-    chosenQualities: any,
+    replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
+    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    failNowOnException: any,
     requestStopDownload: any,
-) {
+): boolean {
     if (!navigator.storage || !navigator.storage.getDirectory) {
-        throw "unsupported";
+        throw new Error("OPFS Api unsupported");
     }
     let saveDir = await navigator.storage.getDirectory();
     if (!saveDir) {
-        return "cancelled";
+        return;
     }
     // clear the directory
     saveDir.removeEntry("videoag", { recursive: true });
     saveDir = await saveDir.getDirectoryHandle("videoag", { create: true });
     if (!saveDir) {
-        return "cancelled";
+        return;
     }
 
-    for (let lecture of course.lectures!) {
-        if (chosenLectures.current.get(lecture.id) && lecture.publish_media) {
-            try {
-                if (requestStopDownload.current) {
-                    replaceStatus(lecture.id, () => ({
-                        downloaded: 0,
-                        total: 0,
-                        done: false,
-                        percent: 0,
-                        error: "Download cancelled",
-                    }));
-                    continue;
-                }
-                let chosenMedium: publish_medium | undefined = undefined;
-                for (let medium of lecture.publish_media) {
-                    if (sourceToName(medium) === chosenQualities.current.get(lecture.id)) {
-                        chosenMedium = medium;
-                        break;
-                    }
-                }
-                if (!chosenMedium || !chosenMedium.url) continue;
-
-                // TODO file ending mp4. title often empty?
-                let filename = `videoag_${course.id}_${lecture.id}_${chosenMedium.title}.mp4`;
-
-                let fileHandle = await saveDir.getFileHandle(filename, { create: true });
-                let writable = await fileHandle.createWritable();
-                let response = await fetch(chosenMedium.url!);
-                if (response.ok === false)
-                    throw new Error("Download failed, status " + response.status);
-                let reader = response.body!.getReader();
-                let writer = writable.getWriter();
-                let lastUpdate = Date.now();
-                let downloaded = 0;
-                let total = chosenMedium.medium_metadata.file_size;
-                replaceStatus(lecture.id, () => ({
-                    downloaded,
-                    total,
+    return downloadToDirectoryHandle(
+        replaceStatus,
+        filesToDownload,
+        failNowOnException,
+        requestStopDownload,
+        saveDir,
+        async (fileHandle) => {
+            // create a element to download the file
+            let a = document.createElement("a");
+            a.href = URL.createObjectURL(await fileHandle.getFile());
+            a.download = filename;
+            a.target = "_blank";
+            a.click();
+        },
+    )
+}
+
+async function downloadWithDownloadAPI(
+    replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
+    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    failNowOnException: any,
+    requestStopDownload: any,
+): boolean {
+    showWarningToast("Unable to store files in directory automatically. Using basic download API which might require" +
+        " you to selected the destination location for every file.");
+    for (const toDownload of filesToDownload) {
+        try {
+            if (requestStopDownload.current) {
+                replaceStatus(toDownload.lectureId, () => ({
+                    downloadedBytes: 0,
+                    totalBytes: 0,
                     done: false,
-                    percent: 0,
-                }));
-                while (true) {
-                    const { done, value } = await reader.read();
-                    if (done || requestStopDownload.current) {
-                        break;
-                    }
-                    await writer.write(value);
-                    downloaded += value!.byteLength;
-                    let now = Date.now();
-                    // after 0.2s, update progress
-                    if (now - lastUpdate > 200) {
-                        replaceStatus(lecture.id, () => ({
-                            downloaded,
-                            total,
-                            done: false,
-                            percent: (downloaded / total) * 100,
-                        }));
-                        lastUpdate = now;
-                    }
-                }
-                await writer.close();
-                if (requestStopDownload.current) {
-                    // remove incomplete file
-                    saveDir.removeEntry(filename);
-                    replaceStatus(lecture.id, (old) => ({
-                        ...old!,
-                        error: "Download cancelled",
-                    }));
-                } else {
-                    // create a element to download the file
-                    let a = document.createElement("a");
-                    a.href = URL.createObjectURL(await fileHandle.getFile());
-                    a.download = filename;
-                    a.target = "_blank";
-                    a.click();
-
-                    replaceStatus(lecture.id, () => ({
-                        downloaded,
-                        total,
-                        done: true,
-                        percent: 100,
-                    }));
-                }
-            } catch (e: any) {
-                replaceStatus(lecture.id, (old) => ({
-                    ...old!,
-                    error: e + "",
+                    error: "Download cancelled",
                 }));
+                continue;
             }
+            let a = document.createElement("a");
+            a.href = toDownload.url;
+            a.download = toDownload.filename;
+            a.target = "_blank";
+            a.click();
+            failNowOnException.current = true;
+        } catch (e: any) {
+            replaceStatus(toDownload.lectureId, (old) => ({
+                ...old!,
+                error: e + "",
+            }));
         }
     }
-    return "success";
+    return true;
 }
 
-async function downloadWithDownloadAPI(
-    replaceStatus: (id: number, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    course: GetCourseResponse,
-    chosenLectures: any,
-    chosenQualities: any,
+async function downloadMedia(
+    downloadStatus: Map<int, DownloadStatus> | undefined,
+    setDownloadStatus: (fn: (old?: Map<int, DownloadStatus> | undefined) => Map<int, DownloadStatus> | undefined) => void,
+    course: course,
+    chosenMediumIdByLectureId: Map<int, int>,
+    failNowOnException: any,
     requestStopDownload: any,
 ) {
-    for (let lecture of course.lectures!) {
-        if (chosenLectures.current.get(lecture.id) && lecture.publish_media) {
-            try {
-                if (requestStopDownload.current) {
-                    replaceStatus(lecture.id, () => ({
-                        downloaded: 0,
-                        total: 0,
-                        done: false,
-                        percent: 0,
-                        error: "Download cancelled",
-                    }));
-                    continue;
-                }
-                let chosenMedium: publish_medium | undefined = undefined;
-                for (let medium of lecture.publish_media) {
-                    if (sourceToName(medium) === chosenQualities.current.get(lecture.id)) {
-                        chosenMedium = medium;
-                        break;
-                    }
-                }
-                if (!chosenMedium || !chosenMedium.url) continue;
-
-                // TODO file ending mp4. title often empty?
-                let filename = `videoag_${course.id}_${lecture.id}_${chosenMedium.title}.mp4`;
-
-                let a = document.createElement("a");
-                a.href = chosenMedium.url!;
-                a.download = filename;
-                a.target = "_blank";
-                a.click();
-            } catch (e: any) {
-                replaceStatus(lecture.id, (old) => ({
-                    ...old!,
-                    error: e + "",
-                }));
+    const filesToDownload = [];
+    for (const lecture of course.lectures!) {
+        const chosenMediumId = chosenMediumIdByLectureId.get(lecture.id);
+        if (chosenMediumId === undefined)
+            continue;
+
+        const medium = lecture.publish_media!.find((m) => m.id == chosenMediumId);
+        if (!medium)
+            throw new Error(`Unable to find medium with id ${chosenMediumId} in lecture`);
+
+        filesToDownload.push({
+            lectureId: lecture.id,
+            url: medium.url,
+            // TODO file ending mp4
+            // TODO lecture date and not id
+            filename: `videoag_${course.handle}_${lecture.id}_${getMediumQualityName(medium)}.mp4`,
+            fileSize: medium.file_size,
+        });
+    }
+
+    setDownloadStatus(new Map());
+    const replaceStatus = (lectureId: int, fn: (old?: DownloadStatus) => DownloadStatus) => {
+        setDownloadStatus((oldStatus) => {
+            const newStatus = new Map(oldStatus);
+            newStatus.set(lectureId, fn(oldStatus?.get(lectureId)));
+            return newStatus;
+        });
+    };
+
+    console.log("Starting download...");
+    failNowOnException.current = false;
+    const success = await downloadWithFileSystemAPI(
+            replaceStatus,
+            filesToDownload,
+            requestStopDownload,
+        ).catch((e) => {
+            console.log("Error with File System Api", e);
+            if (failNowOnException.current) {
+                showError(e, "An error occurred while downloading");
+                return false;
             }
+            return downloadWithOPFSAPI(
+                replaceStatus,
+                filesToDownload,
+                requestStopDownload,
+            );
+        }).catch((e) => {
+            console.log("Error with OPFS Api", e);
+            if (failNowOnException.current) {
+                showError(e, "An error occurred while downloading");
+                return false;
+            }
+            return downloadWithDownloadAPI(
+                replaceStatus,
+                filesToDownload,
+                requestStopDownload,
+            );
+        }).catch((e) => {
+            console.log("Error with Download Api", e);
+            showError(e, "An error occurred while downloading");
+            return false;
+        });
+    console.log(`Download finished. Success: ${success}`)
+    if (success) {
+        showInfoToast("Download finished", true)
+    }
+}
+
+function DownloadAllLectureListItem({
+    lecture,
+    selectedMediumId,
+    setSelectedMediumId,
+    isDownloading,
+    downloadStatus,
+}: {
+    lecture: lecture;
+    selectedMediumId: int | undefined;
+    setSelectedMediumId: (int) => void;
+    isDownloading: boolean;
+    downloadStatus: DownloadStatus | undefined;
+}) {
+    const { language } = useLanguage();
+
+    const media = getSortedDownloadablePublishMedia(lecture.publish_media!);
+
+    let displayedProgress;
+    if (isDownloading && selectedMediumId !== undefined) {
+        // Lecture needs to be downloaded
+        if (downloadStatus === undefined) {
+            // Not yet started
+            displayedProgress = (
+                <div className="spinner-border" role="status">
+                    <span className="visually-hidden">Loading...</span>
+                </div>
+            );
+        } else if (downloadStatus.error) {
+            displayedProgress = (
+                <span className="text-danger">
+                    {downloadStatus.get(lecture.id)?.error}
+                </span>
+            );
+        } else if (downloadStatus.done) {
+            displayedProgress = <span className="text-success">Done</span>;
+        } else {
+            displayedProgress = (
+                <>
+                    <div className="progress" style={{ width: "100px" }}>
+                        <div
+                            className="progress-bar"
+                            role="progressbar"
+                            style={{
+                                width: downloadStatus.percent + "%",
+                            }}
+                            aria-valuenow={downloadStatus.percent}
+                            aria-valuemin={0}
+                            aria-valuemax={100}
+                        />
+                    </div>
+                    <span>
+                        {Math.round(
+                            (downloadStatus.downloaded ?? 0) /
+                                1024 /
+                                1024,
+                        )}{" "}
+                        MB /{" "}
+                        {Math.round(
+                            (downloadStatus.total ?? 0) / 1024 / 1024,
+                        )}{" "}
+                        MB
+                    </span>
+                </>
+            );
         }
+    } else if (isDownloading) {
+        // Lecture should not be downloaded
+        displayedProgress = <></>;
+    } else {
+        const selectedMedium = media.find((m) => m.id == selectedMediumId);
+        displayedProgress = (
+            <a href={selectedMedium?.url} download target="_blank">
+                <button
+                    type="button"
+                    className="btn btn-primary"
+                    disabled={selectedMedium === undefined}
+                >
+                    {language.get("ui.download_manager.download_now")}
+                </button>
+            </a>
+        );
     }
 
-    return "success";
+    return (
+        <tr>
+            <td className="col-1">
+                <input
+                    type="checkbox"
+                    className="form-check-input"
+                    checked={selectedMediumId !== undefined}
+                    disabled={isDownloading || media.length == 0}
+                    onChange={(e) => setSelectedMediumId(media[0].id)}
+                />
+            </td>
+            <td
+                className={
+                    "col-6 make-span-overflow-scroll " +
+                    selectedMediumId !== undefined ? "" : "text-secondary"
+                }
+            >
+                <StylizedText>{lecture.title}</StylizedText>
+            </td>
+            <td className="col-3">
+                <select
+                    className="form-select"
+                    disabled={isDownloading || media.length == 0}
+                    value={selectedMediumId}
+                    onChange={(e) => setSelectedMediumId(e.target.value)}
+                >
+                    {getNamedPublishMedia(media).map(({ name, medium }) => (
+                        <option key={medium.id} value={medium.id}>
+                            {name}
+                        </option>
+                    ))}
+                </select>
+            </td>
+            <td className="col-2 text-center">{displayedProgress}</td>
+        </tr>
+    );
 }
 
-export default function DownloadAllModal({ course }: { course: GetCourseResponse }) {
+export default function DownloadAllModal({ course }: { course: course }) {
     const [showModal, setShowModal] = useState(false);
-    const chosenQualities = useRef(new Map<number, string>());
-    const chosenLectures = useRef(new Map<number, boolean>());
-    const [forceReload, setForceReload] = useState(0);
-    const [downloadStarted, setDownloadStarted] = useState(false);
+    const [chosenMediumIdByLectureId, setChosenMediumIdByLectureId] = useState<Map<int, int>>(new Map());
+
     const requestStopDownload = useRef(false);
-    const [downloadStatus, setDownloadStatus] = useState<Map<number, DownloadStatus>>();
+    const failNowOnException = useRef(false);
+    const [isDownloading, setIsDownloading] = useState(false);
+    const [downloadStatus, setDownloadStatus] = useState<Map<int, DownloadStatus> | undefined>();
     const { language } = useLanguage();
 
     const closeClicked = () => {
-        if (!downloadStarted) setShowModal(false);
+        if (!isDownloading) setShowModal(false);
         else showWarningToast("A download is in progress");
     };
 
     useEffect(() => {
         if (!showModal) return;
 
+        setIsDownloading(false);
         setDownloadStatus(undefined);
         requestStopDownload.current = false;
 
-        chosenQualities.current = new Map<number, string>();
-        chosenLectures.current = new Map<number, boolean>();
-        for (let lecture of course.lectures!) {
-            let sortedsources = sortSources(lecture.publish_media).filter((q) => q.url);
-            chosenLectures.current.set(lecture.id, sortedsources.length > 0);
-            if (sortedsources.length > 0)
-                chosenQualities.current.set(lecture.id, sourceToName(sortedsources[0]));
+        const newChosenMedia = new Map<int, int>();
+        for (const lecture of course.lectures!) {
+            const publishMedia = getSortedDownloadablePublishMedia(lecture.publish_media!)
+            if (publishMedia.length > 0)
+                newChosenMedia.set(lecture.id, publishMedia[0].id);
         }
-        setForceReload(forceReload + 1);
+        setChosenMediumIdByLectureId(newChosenMedia);
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [showModal, course.lectures]);
 
-    if (
-        !course.lectures ||
-        !course.lectures.some(
-            (lecture) =>
-                lecture.publish_media && lecture.publish_media.some((ele) => ele.allow_download),
-        )
-    ) {
-        return <></>;
-    }
-
-    let lectures = [];
-    let allQualities: string[] = [];
+    let allQualityNames: string[] = [];
     let totalSize = 0;
-    for (let lecture of course.lectures!) {
-        let sortedsources = sortSources(lecture.publish_media).filter((q) => q.url);
-
-        for (let source of lecture.publish_media ?? []) {
-            let qName = source.title;
-            if (qName.length === 0) {
-                qName = "<empty>";
-            }
-            if (!allQualities.includes(qName)) {
-                allQualities.push(qName);
-            }
-        }
+    for (const lecture of course.lectures!) {
+        const media = getSortedDownloadablePublishMedia(lecture.publish_media!);
+        const selectedMediumId = chosenMediumIdByLectureId.get(lecture.id);
 
-        let selectedQuality: publish_medium | undefined = undefined;
-        if (chosenQualities.current.get(lecture.id)) {
-            selectedQuality = lecture.publish_media?.find(
-                (source) => sourceToName(source) === chosenQualities.current.get(lecture.id),
-            );
-        }
-        if (selectedQuality && chosenLectures.current.get(lecture.id)) {
-            totalSize += Math.max(0, selectedQuality.medium_metadata.file_size);
-        }
+        for (const medium of media) {
+            const qualityName = getMediumQualityName(medium);
+            if (!allQualityNames.includes(qualityName))
+                allQualityNames.push(qualityName);
 
-        let displayedProgress;
-        if (downloadStatus !== undefined) {
-            if (chosenLectures.current.get(lecture.id) === true) {
-                // Lecture needs to be downloaded
-                if (downloadStatus.get(lecture.id) === undefined) {
-                    // Not yet started
-                    displayedProgress = (
-                        <div className="spinner-border" role="status">
-                            <span className="visually-hidden">Loading...</span>
-                        </div>
-                    );
-                } else {
-                    // started
-                    if (downloadStatus.get(lecture.id)?.error) {
-                        displayedProgress = (
-                            <span className="text-danger">
-                                {downloadStatus.get(lecture.id)?.error}
-                            </span>
-                        );
-                    } else if (downloadStatus.get(lecture.id)?.done) {
-                        displayedProgress = <span className="text-success">Done</span>;
-                    } else {
-                        displayedProgress = (
-                            <>
-                                <div className="progress" style={{ width: "100px" }}>
-                                    <div
-                                        className="progress-bar"
-                                        role="progressbar"
-                                        style={{
-                                            width: downloadStatus.get(lecture.id)?.percent + "%",
-                                        }}
-                                        aria-valuenow={downloadStatus.get(lecture.id)?.percent}
-                                        aria-valuemin={0}
-                                        aria-valuemax={100}
-                                    />
-                                </div>
-                                <span>
-                                    {Math.round(
-                                        (downloadStatus.get(lecture.id)?.downloaded ?? 0) /
-                                            1024 /
-                                            1024,
-                                    )}{" "}
-                                    MB /{" "}
-                                    {Math.round(
-                                        (downloadStatus.get(lecture.id)?.total ?? 0) / 1024 / 1024,
-                                    )}{" "}
-                                    MB
-                                </span>
-                            </>
-                        );
-                    }
-                }
-            } else {
-                // Lecture should not be downloaded
-                displayedProgress = <></>;
-            }
-        } else {
-            displayedProgress = (
-                <a href={selectedQuality?.url} download>
-                    <button
-                        type="button"
-                        className="btn btn-primary"
-                        disabled={chosenQualities.current.get(lecture.id) === undefined}
-                    >
-                        {language.get("ui.download_manager.download_now")}
-                    </button>
-                </a>
-            );
+            if (medium.id === selectedMediumId)
+                totalSize += medium.medium_metadata.file_size;
         }
-
-        lectures.push(
-            <tr key={lecture.id}>
-                <td className="col-1">
-                    <input
-                        type="checkbox"
-                        className="form-check-input"
-                        checked={chosenLectures.current.get(lecture.id) ?? false}
-                        disabled={downloadStarted}
-                        onChange={(e) => {
-                            let nextVal =
-                                e.target.checked &&
-                                (lecture.publish_media ?? []).some((q) => q.url);
-                            chosenLectures.current.set(lecture.id, nextVal);
-                            setForceReload(forceReload + 1);
-                        }}
-                    />
-                </td>
-                <td
-                    className={
-                        "col-6 make-span-overflow-scroll " +
-                        ((chosenLectures.current.get(lecture.id) ?? false) ? "" : "text-secondary")
-                    }
-                >
-                    <StylizedText>{lecture.title}</StylizedText>
-                </td>
-                <td className="col-3">
-                    <select
-                        className="form-select"
-                        disabled={downloadStarted || sortedsources.length === 0}
-                        value={chosenQualities.current.get(lecture.id)}
-                        onChange={(e) => {
-                            chosenQualities.current.set(lecture.id, e.target.value);
-                            setForceReload(forceReload + 1);
-                        }}
-                    >
-                        {sortedsources.map((source) => {
-                            let sourceName = sourceToName(source);
-
-                            return (
-                                <option key={sourceName} value={sourceName}>
-                                    {sourceName}
-                                </option>
-                            );
-                        })}
-                    </select>
-                </td>
-                <td className="col-2 text-center">{displayedProgress}</td>
-            </tr>,
-        );
     }
-    allQualities = sortSourceNames(allQualities);
+    if (allQualityNames.length === 0) {
+        // No media exists which could be downloaded
+        return <></>;
+    }
 
     const selectAll = () => {
-        if (downloadStarted) return;
-        for (let lecture of course.lectures!) {
-            if (lecture.publish_media && lecture.publish_media.some((source) => source.url)) {
-                chosenLectures.current.set(lecture.id, true);
-            }
+        if (isDownloading) return;
+
+        const newChosenMedia = new Map(chosenMediumIdByLectureId);
+        for (const lecture of course.lectures!) {
+            if (newChosenMedia.get(lecture.id))
+                continue;
+            const publishMedia = getSortedDownloadablePublishMedia(lecture.publish_media!)
+            if (publishMedia.length > 0)
+                newChosenMedia.set(lecture.id, publishMedia[0].id);
         }
-        setForceReload(forceReload + 1);
+        setChosenMediumIdByLectureId(newChosenMedia);
     };
     const selectNone = () => {
-        if (downloadStarted) return;
-        for (let lecture of course.lectures!) {
-            chosenLectures.current.set(lecture.id, false);
+        if (isDownloading) return;
+        setChosenMediumIdByLectureId(new Map());
+    };
+    const setLectureToMediumId = (lecture_id: int, medium_id: int) => {
+        if (isDownloading) return;
+
+        const newChosenMedia = new Map(chosenMediumIdByLectureId);
+        newChosenMedia.set(lecture_id, medium_id);
+        setChosenMediumIdByLectureId(newChosenMedia);
+    };
+    const setAllToQuality = (qualityToSet: string) => {
+        if (isDownloading) return;
+
+        const newChosenMedia = new Map(chosenMediumIdByLectureId);
+        for (const lecture of course.lectures!) {
+            if (newChosenMedia.get(lecture.id) === undefined)
+                continue;
+            for (const medium of lecture.publish_media) {
+                if (getMediumQualityName(medium) === qualityToSet)
+                    newChosenMedia.set(lecture.id, medium.id);
+            }
         }
-        setForceReload(forceReload + 1);
+        setChosenMediumIdByLectureId(newChosenMedia);
     };
 
     const startDownloads = async () => {
-        let newDownloadStatus = new Map<number, DownloadStatus>();
-        setDownloadStatus(newDownloadStatus);
-
-        const replaceStatus = (id: number, fn: (old?: DownloadStatus) => DownloadStatus) => {
-            setDownloadStatus((oldStatus) => {
-                let newStatus = new Map(oldStatus);
-                newStatus.set(id, fn(oldStatus?.get(id)));
-                return newStatus;
-            });
-        };
-
-        const success = await downloadWithFileSystemAPI(
-            replaceStatus,
+        if (isDownloading) return;
+        downloadMedia(
+            downloadStatus,
+            setDownloadStatus,
             course,
-            chosenLectures,
-            chosenQualities,
+            chosenMediumIdByLectureId,
+            failNowOnException,
             requestStopDownload,
-        )
-            .catch((e) => {
-                console.log(e);
-                return downloadWithOPFSAPI(
-                    replaceStatus,
-                    course,
-                    chosenLectures,
-                    chosenQualities,
-                    requestStopDownload,
-                );
-            })
-            .catch((e) => {
-                console.log(e);
-                return downloadWithDownloadAPI(
-                    replaceStatus,
-                    course,
-                    chosenLectures,
-                    chosenQualities,
-                    requestStopDownload,
-                );
-            })
-            .catch((e) => {
-                console.log(e);
-                return "error";
-            });
-        console.log(success);
-
-        setDownloadStarted(false);
+        );
+        setIsDownloading(false);
     };
 
     const toggleDownload = () => {
-        if (downloadStarted) {
+        if (isDownloading) {
             requestStopDownload.current = true;
         } else {
-            setDownloadStarted(true);
+            setIsDownloading(true);
             requestStopDownload.current = false;
             startDownloads();
             requestStopDownload.current = false;
         }
     };
 
-    const setAllQualities = (q: string) => {
-        for (let lecture of course.lectures!) {
-            if (lecture.publish_media) {
-                let source = lecture.publish_media.find((s) => s.title === q);
-                if (source) {
-                    chosenQualities.current.set(lecture.id, sourceToName(source));
-                }
-            }
-        }
-        setForceReload(forceReload + 1);
-    };
-
     return (
         <>
             <button type="button" className="btn btn-secondary" onClick={() => setShowModal(true)}>
@@ -584,15 +529,15 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse
                             type="button"
                             className="btn btn-primary"
                             onClick={toggleDownload}
-                            disabled={downloadStarted && requestStopDownload.current}
+                            disabled={isDownloading && requestStopDownload.current}
                         >
                             <i
                                 className={
                                     "bi me-1 " +
-                                    (downloadStarted ? "bi-pause-fill" : "bi-play-fill")
+                                    (isDownloading ? "bi-pause-fill" : "bi-play-fill")
                                 }
                             />
-                            {downloadStarted
+                            {isDownloading
                                 ? language.get("ui.download_manager.stop_download")
                                 : language.get("ui.download_manager.start_download")}
                             {totalSize > 0 && (
@@ -605,7 +550,7 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse
                             type="button"
                             className="btn btn-secondary me-2"
                             onClick={selectAll}
-                            disabled={downloadStarted}
+                            disabled={isDownloading}
                         >
                             {language.get("ui.download_manager.select_all")}
                         </button>
@@ -614,7 +559,7 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse
                             type="button"
                             className="btn btn-secondary me-2"
                             onClick={selectNone}
-                            disabled={downloadStarted}
+                            disabled={isDownloading}
                         >
                             {language.get("ui.download_manager.select_none")}
                         </button>
@@ -624,11 +569,11 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse
                             title={language.get("ui.download_manager.quality")}
                             className=""
                         >
-                            {allQualities.map((q) => (
+                            {allQualityNames.map((q) => (
                                 <DropdownItem
                                     key={q}
                                     type="button"
-                                    onClick={() => setAllQualities(q)}
+                                    onClick={() => setAllToQuality(q)}
                                 >
                                     {q}
                                 </DropdownItem>
@@ -640,14 +585,23 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse
                         className="table table-bordered table-responsive-sm"
                         style={{ tableLayout: "fixed" }}
                     >
-                        <tbody>{lectures}</tbody>
+                        <tbody>
+                            {course.lectures.map((lecture) => (
+                                <DownloadAllLectureListItem
+                                    key={lecture.id}
+                                    lecture={lecture}
+                                    selectedMediumId={chosenMediumIdByLectureId.get(lecture.id)}
+                                    setSelectedMediumId={(medium_id) => setLectureToMediumId(lecture.id, medium_id)}
+                                />
+                            ))}
+                        </tbody>
                     </table>
                 </Modal.Body>
                 <Modal.Footer>
                     <button
                         className="btn btn-secondary"
                         onClick={closeClicked}
-                        disabled={downloadStarted}
+                        disabled={isDownloading}
                     >
                         {language.get("ui.download_manager.close")}
                     </button>
diff --git a/src/videoag/course/Lecture.tsx b/src/videoag/course/Lecture.tsx
index fec1f23437bd062fbe0c12cf627caac4812090e4..2f9975e9678523077ba53776ee9d8826d9d512e1 100644
--- a/src/videoag/course/Lecture.tsx
+++ b/src/videoag/course/Lecture.tsx
@@ -1,7 +1,7 @@
 import Link from "next/link";
 import { OverlayTrigger, Tooltip } from "react-bootstrap";
 
-import type { lecture } from "@/videoag/api/types";
+import type { lecture, course } from "@/videoag/api/types";
 import {
     OMDelete,
     OMEdit,
@@ -9,24 +9,27 @@ import {
     EmbeddedOMFieldComponent,
 } from "@/videoag/object_management/OMConfigComponent";
 import { AuthenticationMethodIcons } from "@/videoag/authentication/ViewPermissions";
+import { datetimeToString } from "@/videoag/miscellaneous/Formatting";
+import { StylizedText } from "@/videoag/miscellaneous/StylizedText";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
 
-import { DownloadSources, EditSources } from "./Player";
+import { PublishMediumDownloadButton, PublishMediumList } from "./Medium";
+import { LectureLiveLabel } from "./LiveLabel";
 
 export function urlForLecture(course_id: string, lecture_id: string | number) {
     return `${course_id}/${lecture_id}`;
 }
 
 export function getLectureThumbnailUrl(lecture: lecture): string | undefined {
-    if (!lecture.is_authenticated) return undefined;
-
-    for (let medium of lecture.publish_media ?? []) {
-        if (medium.medium_metadata.type === "thumbnail") {
-            return medium.url;
+    if (lecture.is_authenticated) {
+        for (let medium of lecture.publish_media ?? []) {
+            if (medium.medium_metadata.type === "thumbnail") {
+                return medium.url;
+            }
         }
     }
-    return undefined;
+    return "/static/empty_thumbnail.png";
 }
 
 export function LectureListItem({
@@ -43,13 +46,7 @@ export function LectureListItem({
     const { editMode } = useEditMode();
     const { language } = useLanguage();
 
-    const showDownloadSourcesButton =
-        editMode === false &&
-        lecture.publish_media &&
-        lecture.publish_media.some((ele) => ele.url !== undefined);
-
     const thumbnailUrl = getLectureThumbnailUrl(lecture);
-    // TODO add link when any viewable medium, even if no thumbnail
 
     return (
         <li
@@ -58,24 +55,24 @@ export function LectureListItem({
             {...props}
         >
             <div className="row">
-                {thumbnailUrl !== undefined ? (
+                {lecture.publish_media?.some((m) => m.include_in_player) ? (
                     <div
                         style={{
                             backgroundImage: `url('${thumbnailUrl}')`,
+                            height: "8em",
                         }}
-                        className="col-sm-2 col-12 thumbnailimg"
+                        className="col-md-2 col-12 center-background-img position-relative h-5"
                     >
                         <Link href={`/${course_handle}/${lecture.id}`}>
-                            <span
-                                aria-hidden="true"
-                                className={"playpreviewbtn bi bi-play-circle"}
-                            />
+                            <span className="centered-overlay-container" aria-hidden="true">
+                                <span className={"bi bi-play-circle fs-1"} style={{ color: "lightgrey" }} />
+                            </span>
                         </Link>
                     </div>
                 ) : (
-                    <div className="col-sm-2 col-12"></div>
+                    <div className="col-md-2 col-12"></div>
                 )}
-                <ul className="list-unstyled col-sm-3 col-12">
+                <ul className="list-unstyled col-md-3 col-12">
                     <li>
                         <EmbeddedOMFieldComponent
                             object_type="lecture"
@@ -85,7 +82,7 @@ export function LectureListItem({
                             initialValue={lecture.title}
                         />
                     </li>
-                    {lecture.speaker && (
+                    {(editMode || lecture.speaker) && (
                         <li>
                             {language.get("ui.generic.lecture_given_by")}{" "}
                             <EmbeddedOMFieldComponent
@@ -109,90 +106,108 @@ export function LectureListItem({
                             initialValue={lecture.time}
                         />
                     </li>
-                    {lecture.internal_comment && lecture.internal_comment.trim().length > 0 && (
-                        <li className="text-muted">
+                </ul>
+                <ul className="list-unstyled col-md-3 col-12">
+                    <li className="d-flex mb-1">
+                        {editMode && (
                             <OverlayTrigger
                                 overlay={
                                     <Tooltip>
-                                        {language.get("object.lecture.internal_comment")}
+                                        {language.get("object.lecture.description")}
                                     </Tooltip>
                                 }
                             >
-                                <span className="bi bi-chat-left-quote" />
+                                <span className="bi bi-chat-left-quote ms-1 me-1" />
                             </OverlayTrigger>
-
+                        )}
+                        <span style={{ width: 0, flexGrow: 1 }}>
                             <EmbeddedOMFieldComponent
                                 object_type="lecture"
                                 object_id={lecture.id!}
-                                field_id="internal_comment"
+                                field_id="description"
                                 field_type="string"
-                                initialValue={lecture.internal_comment}
+                                initialValue={lecture.description}
                                 allowMarkdown={true}
                             />
-                        </li>
-                    )}
-                </ul>
-                <ul className="list-unstyled col-sm-3 col-12">
-                    <li>
-                        <EmbeddedOMFieldComponent
-                            object_type="lecture"
-                            object_id={lecture.id!}
-                            field_id="description"
-                            field_type="string"
-                            initialValue={lecture.description}
-                            allowMarkdown={true}
-                        />
+                        </span>
                     </li>
-                    {show_chapters &&
-                        lecture.chapters?.map((chapter) => (
-                            <li
-                                key={chapter.name}
-                                className={
-                                    "d-flex " +
-                                    (chapter.visible === false ? "bg-danger-subtle rounded" : "")
+                    {(editMode || lecture.internal_comment) && (
+                        <li className="text-muted bg-danger-subtle d-flex mb-1" style={{ borderRadius: "0.3em" }}>
+                            <OverlayTrigger
+                                overlay={
+                                    <Tooltip>
+                                        {language.get("object.lecture.internal_comment")}
+                                    </Tooltip>
                                 }
                             >
-                                <span className="bi bi-play" />
-                                <Link
-                                    href={`/${course_handle}/${lecture.id}?t=` + chapter.start_time}
-                                >
-                                    {chapter.name}
-                                </Link>
-                                {editMode && (
-                                    <>
-                                        <div className="flex-fill" />
-                                        <EmbeddedOMFieldComponent
-                                            object_type="chapter"
-                                            object_id={chapter.id!}
-                                            field_id="visible"
-                                            field_type="boolean"
-                                            initialValue={chapter.visible}
-                                            className="me-2 align-self-center py-0"
-                                        />
-                                        <OMDelete
-                                            object_type="chapter"
-                                            object_id={chapter.id!}
-                                            className="py-0"
-                                        />
-                                    </>
-                                )}
-                            </li>
-                        ))}
-                </ul>
-                <ul className="col-sm-4 col-12 list-unstyled d-flex">
-                    {showDownloadSourcesButton && (
-                        <DownloadSources
-                            publish_media={lecture.publish_media ?? []}
-                            direction="down"
-                            tabIndex="-1"
-                        />
+                                <span className="bi bi-chat-left-quote ms-1 me-1" />
+                            </OverlayTrigger>
+                            <span style={{ width: 0, flexGrow: 1 }}>
+                                <EmbeddedOMFieldComponent
+                                    object_type="lecture"
+                                    object_id={lecture.id!}
+                                    field_id="internal_comment"
+                                    field_type="string"
+                                    initialValue={lecture.internal_comment}
+                                    allowMarkdown={true}
+                                />
+                            </span>
+                        </li>
                     )}
-                    {editMode && lecture.publish_media && (
-                        <EditSources publish_media={lecture.publish_media} />
+                    {show_chapters && lecture.chapters && (
+                        <>
+                            {lecture.chapters?.map((chapter) => (
+                                <li
+                                    key={chapter.name}
+                                    className={
+                                        "d-flex " +
+                                        (chapter.visible === false ? "bg-danger-subtle rounded" : "")
+                                    }
+                                >
+                                    <span className="bi bi-play" />
+                                    <Link
+                                        href={`/${course_handle}/${lecture.id}?t=` + chapter.start_time}
+                                    >
+                                        {chapter.name}
+                                    </Link>
+                                    {editMode && (
+                                        <>
+                                            <div className="flex-fill" />
+                                            <EmbeddedOMFieldComponent
+                                                object_type="chapter"
+                                                object_id={chapter.id!}
+                                                field_id="visible"
+                                                field_type="boolean"
+                                                initialValue={chapter.visible}
+                                                className="me-2 align-self-center py-0"
+                                            />
+                                            <OMDelete
+                                                object_type="chapter"
+                                                object_id={chapter.id!}
+                                                className="py-0"
+                                            />
+                                        </>
+                                    )}
+                                </li>
+                            ))}
+                            <li className="mb-1" />
+                        </>
                     )}
-
-                    <li className="flex-fill mx-1" />
-                    <li className="p-1">
+                </ul>
+                <ul className="list-unstyled col-md-4 col-12 pe-0 row align-content-start">
+                    <li className="col-xl-7 col-12 p-0">
+                        {editMode ? (
+                            <PublishMediumList publish_media={lecture.publish_media} />
+                        ) : (
+                            <PublishMediumDownloadButton
+                                publish_media={lecture.publish_media ?? []}
+                                direction="down"
+                                tabIndex="-1"
+                                className="pl-1"
+                            />
+                        )}
+                    </li>
+                    <li className="col-xl-5 col-12 p-0 mt-1 d-flex flex-wrap justify-content-end align-content-start align-items-center">
                         {editMode && (
                             <>
                                 <EmbeddedOMFieldComponent
@@ -224,3 +239,102 @@ export function LectureListItem({
         </li>
     );
 }
+
+export function LectureCard({
+    lecture,
+    course,
+    size,
+}: {
+    lecture: lecture;
+    course: course;
+    size?: "small" | "auto";
+}) {
+    const { language } = useLanguage();
+    let dateStr = datetimeToString(lecture.time);
+
+    let sz = size === "small" ? "xxl" : "sm";
+
+    return (
+        <Link
+            href={urlForLecture(course.handle, lecture.id)}
+            title={course.full_name}
+            className="disable-highlight"
+        >
+            {/* display width >= sz */}
+            <div className={`d-none d-${sz}-block`}>
+                <div className="row">
+                    <div className="col-4 position-relative" style={{
+                        maxHeight: "6em",
+                        maxWidth: "11em",
+                    }}>
+                        <img
+                            style={{
+                                height: "100%",
+                                width: "100%",
+                                objectFit: "contain",
+                            }}
+                            src={getLectureThumbnailUrl(lecture)}
+                            alt="Vorschaubild"
+                        />
+                        <span className="centered-overlay-container" aria-hidden="true">
+                            <span className={"bi bi-play-circle fs-3"} style={{ color: "lightgrey" }} />
+                        </span>
+                    </div>
+                    <div className="col-5 p-0">
+                        <span>
+                            <strong>{course.short_name}</strong>{" "}
+                            <LectureLiveLabel lecture={lecture} />
+                        </span>{" "}
+                        <br />
+                        <br />
+                        <span>{dateStr}</span>
+                        {lecture.speaker ? (
+                            <div className="small p-children-inline">
+                                {language.get("ui.generic.lecture_given_by")}{" "}
+                                <StylizedText markdown>{lecture.speaker}</StylizedText>
+                            </div>
+                        ) : null}
+                    </div>
+                    <div className="col-3">
+                        <div>
+                            <StylizedText markdown>{lecture.title}</StylizedText>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            {/* display width < sz */}
+            <div className={`d-block d-${sz}-none`}>
+                <ul className="list-unstyled">
+                    <li className="justify-content-center d-flex mb-1 position-relative">
+                        <img
+                            style={{
+                                maxHeight: "6em",
+                                maxWidth: "11em",
+                                objectFit: "contain",
+                            }}
+                            src={getLectureThumbnailUrl(lecture)}
+                            alt="Vorschaubild"
+                        />
+                        <span className="centered-overlay-container" aria-hidden="true">
+                            <span className={"bi bi-play-circle fs-3"} style={{ color: "lightgrey" }} />
+                        </span>
+                    </li>
+                    <li>
+                        <strong>{course.full_name}</strong>
+                        <LectureLiveLabel lecture={lecture} />
+                    </li>
+                    <li>{dateStr}</li>
+                    {lecture.speaker ? (
+                        <div className="small p-children-inline">
+                            {language.get("ui.generic.lecture_given_by")}{" "}
+                            <StylizedText markdown>{lecture.speaker}</StylizedText>
+                        </div>
+                    ) : null}
+                    <li className="disable-last-paragraph-spacing">
+                        <StylizedText markdown>{lecture.title}</StylizedText>
+                    </li>
+                </ul>
+            </div>
+        </Link>
+    );
+}
diff --git a/src/videoag/course/Medium.tsx b/src/videoag/course/Medium.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e61da73ce754e29b667e99e362c41b27262b41be
--- /dev/null
+++ b/src/videoag/course/Medium.tsx
@@ -0,0 +1,180 @@
+import { Dropdown } from "react-bootstrap";
+
+import {
+    OMDelete,
+    OMEdit,
+    OMHistory,
+    EmbeddedOMFieldComponent,
+} from "@/videoag/object_management/OMConfigComponent";
+import { useEditMode } from "@/videoag/object_management/EditModeProvider";
+import { useLanguage } from "@/videoag/localization/LanguageProvider";
+import { filesizeToHuman } from "@/videoag/miscellaneous/Formatting";
+
+export function getMediumQualityName(medium: publish_medium): string | undefined {
+    switch(medium.medium_metadata.type) {
+        case "plain_video":
+            return `${medium.medium_metadata.vertical_resolution}p`;
+        case "plain_audio":
+            return `${medium.medium_metadata.audio_sample_rate} Hz`;
+        default:
+            return undefined;
+    }
+}
+
+export interface NamedPublishMedium {
+    name: string;
+    medium: publish_medium;
+}
+
+
+export function getNamedPublishMedia(media: publish_medium[]): NamedPublishMedium[] {
+    // TODO Add more properties from metadata in case non-unique names are generated by this
+
+    const namedMedia = [];
+    for (const medium of media) {
+        const nameParts = []
+        if (medium.title)
+            nameParts.push(medium.title);
+
+        const qualityName = getMediumQualityName(medium);
+        if (qualityName)
+            nameParts.push(qualityName);
+
+        nameParts.push(`(${filesizeToHuman(medium.medium_metadata.file_size)})`);
+
+        namedMedia.push({
+            name: nameParts.join(" "),
+            medium,
+        });
+    }
+
+    return namedMedia;
+}
+
+export function sortPublishMediaByQuality(media: publish_medium[]): publish_medium[] {
+    media.sort((a, b) => {
+        // TODO one could compare more values here
+
+        if (a.medium_metadata.type === "plain_video") {
+            if (b.medium_metadata.type !== "plain_video") {
+                return -1; // a before b
+            }
+
+            return b.medium_metadata.vertical_resolution! - a.medium_metadata.vertical_resolution!;
+        }
+        if (b.medium_metadata.type === "plain_video") {
+            return 1; // b before a
+        }
+        // Both are audio only
+        return b.medium_metadata.audio_sample_rate! - a.medium_metadata.audio_sample_rate!;
+    });
+    return media;
+}
+
+export function getSortedDownloadablePublishMedia(media: publish_medium[]): publish_medium[] {
+    return sortPublishMediaByQuality(media.filter((m) => m.allow_download));
+}
+
+export function PublishMediumList({ publish_media }: { publish_media: publish_medium[] }) {
+    const { editMode } = useEditMode();
+
+    return (
+        <ul className="p-0">
+            {getNamedPublishMedia(sortPublishMediaByQuality(publish_media)).map(({ medium, name }) => (
+                <li
+                    key={medium.id}
+                    className={
+                        "d-flex border align-items-center px-2 py-1 rounded " +
+                        (medium.visible === false ? "bg-danger-subtle" : "")
+                    }
+                >
+                    <span className="bi bi-download" />
+                    <a href={medium.url} className="mx-1">
+                        {name}
+                    </a>
+                    {editMode && (
+                        <>
+                            <div className="flex-fill" />
+                            <EmbeddedOMFieldComponent
+                                object_type="publish_medium"
+                                object_id={medium.id!}
+                                field_id="visible"
+                                field_type="boolean"
+                                initialValue={medium.visible}
+                                className="me-2 align-self-center py-0"
+                            />
+                            <OMDelete
+                                object_type="publish_medium"
+                                object_id={medium.id!}
+                                className="py-0"
+                            />
+                        </>
+                    )}
+                </li>
+            ))}
+        </ul>
+    );
+}
+
+export function PublishMediumDownloadButton({
+    publish_media,
+    className,
+    direction,
+    ...props
+}: {
+    publish_media: publish_medium[];
+    className?: string;
+    direction: "up" | "down";
+    [key: string]: any;
+}) {
+    const { editMode } = useEditMode();
+    const { language } = useLanguage();
+
+    const namedMedia = getNamedPublishMedia(getSortedDownloadablePublishMedia(publish_media));
+    if(namedMedia.length === 0)
+        return (<></>);
+
+    return (
+        <Dropdown drop={direction}>
+            <Dropdown.Toggle
+                variant="primary"
+                disabled={publish_media === undefined || publish_media.length === 0}
+                {...props}
+            >
+                {language.get("ui.generic.download")}
+            </Dropdown.Toggle>
+
+            <Dropdown.Menu>
+                {namedMedia.map(({ medium, name }) => {
+                    let bgColor = "";
+                    if (medium.visible === false)
+                        bgColor = "bg-danger-subtle";
+
+                    return (
+                        <div
+                            key={medium.id}
+                            className={"d-flex align-items-center " + bgColor}
+                        >
+                            <Dropdown.Item href={medium.url} download>
+                                {name}
+                            </Dropdown.Item>
+                            {editMode && (
+                                <>
+                                    <EmbeddedOMFieldComponent
+                                        object_type="publish_medium"
+                                        object_id={medium.id!}
+                                        field_id="visible"
+                                        field_type="boolean"
+                                        initialValue={medium.visible}
+                                        className="me-2"
+                                    />
+                                    <OMDelete object_type="publish_medium" object_id={medium.id!} />
+                                </>
+                            )}
+                        </div>
+                    );
+                })}
+            </Dropdown.Menu>
+        </Dropdown>
+    );
+}
\ No newline at end of file
diff --git a/src/videoag/course/Player.tsx b/src/videoag/course/Player.tsx
index 553cbf2a9a071c9ce9a82fd6740da30e73e53bfe..a48e8a34b4784aef09257da151b2bafea816d2f0 100644
--- a/src/videoag/course/Player.tsx
+++ b/src/videoag/course/Player.tsx
@@ -41,8 +41,7 @@ import {
 } from "@/videoag/object_management/OMConfigComponent";
 import { basePath } from "@/../basepath";
 
-import { VideoCard } from "./VideoCard";
-import { urlForLecture, getLectureThumbnailUrl } from "./Lecture";
+import { LectureCard, urlForLecture, getLectureThumbnailUrl } from "./Lecture";
 
 import { PlayerData } from "@/pages/dynamic_player";
 
@@ -82,28 +81,13 @@ function initPlayer(
     player_media.filter((medium) =>
         ["plain_video", "plain_audio"].includes(medium.medium_metadata.type),
     );
-    player_media.sort((a, b) => {
-        // TODO one could compare more values here
+    sortPublishMediaByQuality(player_media);
 
-        if (a.medium_metadata.type === "plain_video") {
-            if (b.medium_metadata.type !== "plain_video") {
-                return -1; // a before b
-            }
-
-            return b.medium_metadata.vertical_resolution! - a.medium_metadata.vertical_resolution!;
-        }
-        if (b.medium_metadata.type === "plain_video") {
-            return 1; // b before a
-        }
-        // Both are audio only
-        return b.medium_metadata.audio_sample_rate! - a.medium_metadata.audio_sample_rate!;
-    });
-
-    const sources = player_media.map((medium) => {
+    const sources = getNamedPublishMedia(player_media).map(({ name, medium }) => {
         return {
             src: medium.url,
             type: medium.medium_metadata.type === "plain_video" ? "video/mp4" : "audio/mpeg",
-            label: medium.title,
+            label: name,
             selected: false,
         };
     });
@@ -652,7 +636,7 @@ function VideoSuggestions({ course, lecture }: { course: course; lecture: lectur
                         {language.get("ui.video_player.previous_lecture")}
                     </Link>
                     <div className="card-body">
-                        <VideoCard course={course} lecture={prevLecture} size="small" />
+                        <LectureCard course={course} lecture={prevLecture} size="small" />
                     </div>
                     {prevLecture.visible === false && (
                         <div className="card-footer text-center text-muted">
@@ -673,7 +657,7 @@ function VideoSuggestions({ course, lecture }: { course: course; lecture: lectur
                         <i className="bi bi-arrow-right-circle ms-2" />
                     </Link>
                     <div className="card-body">
-                        <VideoCard course={course} lecture={nextLecture} size="small" />
+                        <LectureCard course={course} lecture={nextLecture} size="small" />
                     </div>
                     {nextLecture.visible === false && (
                         <div className="card-footer text-center text-muted">
@@ -688,139 +672,6 @@ function VideoSuggestions({ course, lecture }: { course: course; lecture: lectur
 
 // TODO move and rework
 
-export function sourceToName(source: publish_medium) {
-    // TODO: implement this
-    //if (source.comment.length == 0)
-    return `${source.title} (${filesizeToHuman(source.medium_metadata.file_size)})`;
-
-    //return `${source.quality.name}, ${source.comment} (${filesizeToHuman(source.size)})`;
-}
-
-export function sortSources(sources?: publish_medium[]) {
-    return Array.from(sources ?? []).sort((a, b) => {
-        let aq = parseInt(a.title.split("p")[0]);
-        let bq = parseInt(b.title.split("p")[0]);
-        if (aq === bq) {
-            return (a.id ?? 0) - (b.id ?? 0);
-        }
-        return bq - aq;
-    });
-}
-
-export function sortSourceNames(sourceNames?: string[]) {
-    return Array.from(sourceNames ?? []).sort((a, b) => {
-        let aq = parseInt(a.split("p")[0]);
-        let bq = parseInt(b.split("p")[0]);
-        if (aq === bq) {
-            return a.localeCompare(b);
-        }
-        return bq - aq;
-    });
-}
-
-export function EditSources({ publish_media }: { publish_media: publish_medium[] }) {
-    const { editMode } = useEditMode();
-
-    let sortedsources = sortSources(publish_media);
-
-    return (
-        <ul>
-            {sortedsources.map((source) => (
-                <li
-                    key={source.id}
-                    className={
-                        "d-flex border align-items-center px-2 py-1 rounded " +
-                        (source.visible === false ? "bg-danger-subtle" : "")
-                    }
-                >
-                    <span className="bi bi-download" />
-                    <a href={source.url} className="mx-1">
-                        {sourceToName(source)}
-                    </a>
-                    {editMode && (
-                        <>
-                            <div className="flex-fill" />
-                            <EmbeddedOMFieldComponent
-                                object_type="publish_medium"
-                                object_id={source.id!}
-                                field_id="visible"
-                                field_type="boolean"
-                                initialValue={source.visible}
-                                className="me-2 align-self-center py-0"
-                            />
-                            <OMDelete
-                                object_type="publish_medium"
-                                object_id={source.id!}
-                                className="py-0"
-                            />
-                        </>
-                    )}
-                </li>
-            ))}
-        </ul>
-    );
-}
-
-export function DownloadSources({
-    publish_media,
-    className,
-    direction,
-    ...props
-}: {
-    publish_media: publish_medium[];
-    className?: string;
-    direction: "up" | "down";
-    [key: string]: any;
-}) {
-    const { editMode } = useEditMode();
-    const { language } = useLanguage();
-
-    let sortedsources = sortSources(publish_media);
-
-    return (
-        <Dropdown drop={direction}>
-            <Dropdown.Toggle
-                variant="primary"
-                disabled={publish_media === undefined || publish_media.length === 0}
-                {...props}
-            >
-                {language.get("ui.generic.download")}
-            </Dropdown.Toggle>
-
-            <Dropdown.Menu>
-                {sortedsources.map((v, ind) => {
-                    if (v.url === undefined) return null;
-                    let bgColor = "";
-                    if (v.visible === false) bgColor = "bg-danger-subtle";
-
-                    return (
-                        <div
-                            key={ind + "#" + v.title}
-                            className={"d-flex align-items-center " + bgColor}
-                        >
-                            <Dropdown.Item href={v.url} download>
-                                {sourceToName(v)}
-                            </Dropdown.Item>
-                            {editMode && (
-                                <>
-                                    <EmbeddedOMFieldComponent
-                                        object_type="publish_medium"
-                                        object_id={v.id!}
-                                        field_id="visible"
-                                        field_type="boolean"
-                                        initialValue={v.visible}
-                                        className="me-2"
-                                    />
-                                    <OMDelete object_type="publish_medium" object_id={v.id!} />
-                                </>
-                            )}
-                        </div>
-                    );
-                })}
-            </Dropdown.Menu>
-        </Dropdown>
-    );
-}
 
 export function EmbeddedPlayer({
     playerData,
@@ -1017,7 +868,7 @@ export default function Player({
                         {lecture.publish_media &&
                             lecture.publish_media.some((ele) => ele.url !== undefined) && (
                                 <li className="list-inline-item">
-                                    <DownloadSources
+                                    <DownloadMedia
                                         className="list-inline-item"
                                         direction="up"
                                         publish_media={lecture.publish_media ?? []}
diff --git a/src/videoag/course/VideoCard.tsx b/src/videoag/course/VideoCard.tsx
deleted file mode 100644
index 744c5a353e1f39ad19a527de67b2db0c3e026ee4..0000000000000000000000000000000000000000
--- a/src/videoag/course/VideoCard.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import Link from "next/link";
-
-import type { lecture, course } from "@/videoag/api/types";
-import { useApi } from "@/videoag/api/ApiProvider";
-import { datetimeToString } from "@/videoag/miscellaneous/Formatting";
-import { StylizedText } from "@/videoag/miscellaneous/StylizedText";
-import { useLanguage } from "@/videoag/localization/LanguageProvider";
-
-import { urlForLecture, getLectureThumbnailUrl } from "./Lecture";
-import { LectureLiveLabel } from "./LiveLabel";
-
-export function VideoCard({
-    lecture,
-    course,
-    size,
-}: {
-    lecture: lecture;
-    course: course;
-    size?: "small" | "auto";
-}) {
-    const api = useApi();
-    const { language } = useLanguage();
-    let dateStr = datetimeToString(lecture.time);
-
-    let sz = size === "small" ? "xxl" : "sm";
-
-    const thumbnailUrl = getLectureThumbnailUrl(lecture);
-
-    return (
-        <Link
-            href={urlForLecture(course.handle, lecture.id)}
-            title={course.full_name}
-            className="disable-highlight"
-        >
-            {/* display width >= sz */}
-            <div className={`d-none d-${sz}-block`}>
-                <div className="row">
-                    <img
-                        className="col-4"
-                        width="170"
-                        height="100"
-                        style={{
-                            maxHeight: "120px",
-                            height: "auto",
-                            width: "170px",
-                        }}
-                        src={thumbnailUrl}
-                        alt="Vorschaubild"
-                    />
-                    <div className="col-4">
-                        <span>
-                            <strong>{course.short_name}</strong>{" "}
-                            <LectureLiveLabel lecture={lecture} />
-                        </span>{" "}
-                        <br />
-                        <br />
-                        <span>{dateStr}</span>
-                        {lecture.speaker ? (
-                            <div className="small p-children-inline">
-                                {language.get("ui.generic.lecture_given_by")}{" "}
-                                <StylizedText markdown>{lecture.speaker}</StylizedText>
-                            </div>
-                        ) : null}
-                    </div>
-                    <div className="col-4">
-                        <div>
-                            <StylizedText markdown>{lecture.title}</StylizedText>
-                        </div>
-                    </div>
-                </div>
-            </div>
-            {/* display width < sz */}
-            <div className={`d-block d-${sz}-none`}>
-                <ul className="list-unstyled">
-                    <li className="d-flex justify-content-center mb-1">
-                        <img width="170" height="100" src={thumbnailUrl} alt="Vorschaubild" />
-                    </li>
-                    <li>
-                        <strong>{course.full_name}</strong>
-                        <LectureLiveLabel lecture={lecture} />
-                    </li>
-                    <li>{dateStr}</li>
-                    {lecture.speaker ? (
-                        <div className="small p-children-inline">
-                            {language.get("ui.generic.lecture_given_by")}{" "}
-                            <StylizedText markdown>{lecture.speaker}</StylizedText>
-                        </div>
-                    ) : null}
-                    <li className="disable-last-paragraph-spacing">
-                        <StylizedText markdown>{lecture.title}</StylizedText>
-                    </li>
-                </ul>
-            </div>
-        </Link>
-    );
-}