diff --git a/package.json b/package.json
index 3e8939b1a144039470149380c267b46fda7d6282..ef5ea4772bc8c745271bf2b558c112b983110526 100644
--- a/package.json
+++ b/package.json
@@ -112,6 +112,7 @@
         "md5": "^2.3.0",
         "mini-css-extract-plugin": "1.3.1",
         "mitt": "2.1.0",
+        "mp3tag.js": "3.7.1",
         "multiselect": "0.9.12",
         "pdfjs-dist": "^2.6.347",
         "portal-vue": "^2.1.7",
diff --git a/resources/assets/stylesheets/scss/courseware/blocks/audio.scss b/resources/assets/stylesheets/scss/courseware/blocks/audio.scss
index c5c530ed72607dd1cfbf161351fa01b34f246f6a..2ea944b3f24189704a348425ddfddb4a2ad48694 100644
--- a/resources/assets/stylesheets/scss/courseware/blocks/audio.scss
+++ b/resources/assets/stylesheets/scss/courseware/blocks/audio.scss
@@ -1,231 +1,205 @@
-@use '../../../mixins.scss' as *;
-
-$media-buttons: (
-    play: play,
-    stop: stop,
-    pause: pause,
-    prev: arr_eol-left,
-    next: arr_eol-right
-);
-
 .cw-block-audio {
     .cw-audio-container {
+        display: flex;
+        flex-direction: row;
+        flex-wrap: wrap;
+        justify-content: space-between;
         border: solid thin var(--content-color-40);
-        padding-top: 1em;
-    }
-    .cw-audio-controls {
-        text-align: right;
-        padding: 0 0.5em;
-    }
-    .cw-audio-range {
-        margin: 0 5px 10px 0;
-        &::-moz-focus-outer {
-            border: 0;
-        }
-        &.ui-widget-content {
-            background-color: var(--base-color);
-        }
-        .ui-widget-header {
-            background-color: var(--dark-gray-color-5);
-        }
-        .ui-slider-handle {
-            border-radius: 20px;
-            width: 1em;
-            height: 1.7em;
-            top: -0.5em;
-            background-color: var(--dark-gray-color-20);
-            border-color: var(--content-color-40);
-            cursor: pointer;
-            margin-left: -2px;
-        }
+        padding: 36px;
+        gap: 64px;
     }
-    .cw-audio-button {
-        border: solid thin var(--content-color-40);
-        background-color: var(--white);
-        background-repeat: no-repeat;
-        background-position: center center;
-        background-size: 24px;
-        min-height: 27px;
-        line-height: 130%;
-        padding: 5px 15px 5px 30px;
-        cursor: pointer;
-        font-size: 14px;
-        box-sizing: border-box;
-        text-align: center;
-        text-decoration: none;
-        vertical-align: bottom;
-        white-space: nowrap;
-        min-width: unset;
-        margin: 5px;
-        height: 46px;
-        width: 46px;
-        display: inline-block;
-
-        &:hover {
-            background-color: var(--base-color);
-        }
 
-        @each $button, $icon in $media-buttons {
-            &.cw-audio-#{$button}button {
-                @include background-icon($icon, clickable, 24);
-                &:hover {
-                    @include background-icon($icon, info-alt, 24);
-                }
-            }
+    .cw-audio-recorder,
+    .cw-audio-player {
+        display: flex;
+        flex-direction: row;
+        flex-wrap: wrap;
+        gap: 36px;
+        flex-grow: 100;
+
+        &.with-playlist {
+            flex-direction: column;
         }
-    }
 
-    .cw-audio-time {
-        position: relative;
-        top: -1em;
-        color: var(--base-gray);
-    }
+        .cw-audio-cover {
+            margin: 0 auto;
+            display: flex;
+            flex-direction: row;
 
-    .cw-audio-range {
-        display: block;
-        margin: 0 auto 1.5em;
-        -webkit-appearance: none;
-        position: relative;
-        overflow: hidden;
-        height: 18px;
-        width: 100%;
-        cursor: pointer;
-        border-radius: 0;
-    }
+            &.with-edit-button {
+                position: relative;
+                right: -8px;
+            }
 
-    .cw-audio-range::-webkit-slider-runnable-track {
-        background: var(--dark-gray-color-20);
-    }
+            .cover {
+                width: 256px;
+                height: 256px;
+                object-fit: cover;
+            }
 
-    .cw-audio-range::-webkit-slider-thumb {
-        -webkit-appearance: none;
-        width: 9px; /* 1 */
-        height: 18px;
-        background: var(--white);
-        box-shadow: -100vw 0 0 100vw var(--base-color);
-        border: solid thin var(--content-color-40);
-    }
+            .default-cover {
+                padding: 64px;
+                border: solid thin var(--content-color-40);
+            }
 
-    .cw-audio-range::-moz-range-track {
-        height: 18px;
-        background: var(--dark-gray-color-10);
-    }
+            &.loading {
+                img {
+                    visibility: hidden;
+                }
+            }
 
-    .cw-audio-range::-moz-range-thumb {
-        background: var(--white);
-        height: 18px;
-        width: 9px;
-        border: solid thin var(--content-color-40);
-        border-radius: 0 !important;
-        box-shadow: -100vw 0 0 100vw var(--base-color);
-        box-sizing: border-box;
-    }
+            button {
+                width: 16px;
+                height: 16px;
+                cursor: pointer;
+                padding: 0 8px;
+                background-color: transparent;
+                border: none;
+            }
+        }
 
-    .cw-audio-range::-ms-fill-lower {
-        background: var(--base-color);
-    }
+        .cw-audio-controls-wrapper {
+            flex-grow: 1;
+            min-width: 256px;
+            text-align: center;
+            display: flex;
+            flex-direction: column;
+
+            .cw-audio-current-track {
+                flex-grow: 1;
+                max-width: 270px;
+                min-height: 60px;
+                margin: 0 auto;
+
+                h2,
+                h3 {
+                    margin-top: 0;
+                }
+            }
 
-    .cw-audio-range::-ms-thumb {
-        background: var(--white);
-        border: solid thin var(--content-color-40);
-        height: 18px;
-        width: 9px;
-        box-sizing: border-box;
-    }
+            .cw-audio-controls {
+                .cw-audio-progress {
+                    .cw-audio-range {
+                        width: 100%;
+                        -webkit-appearance: none;
+                        appearance: none;
+                        cursor: pointer;
+                        outline: none;
+                        height: 2px;
+                        background: var(--content-color-40);
+
+                        &::-webkit-slider-thumb {
+                            -webkit-appearance: none;
+                            appearance: none;
+                            height: 16px;
+                            width: 16px;
+                            background-color: var(--base-color);
+                            border-radius: 50%;
+                            border: none;
+                        }
 
-    .cw-audio-range::-ms-ticks-after {
-        display: none;
-    }
+                        &::-moz-range-thumb {
+                            height: 16px;
+                            width: 16px;
+                            background-color: var(--base-color);
+                            border-radius: 50%;
+                            border: none;
+                        }
+                    }
 
-    .cw-audio-range::-ms-ticks-before {
-        display: none;
-    }
+                    .cw-audio-time {
+                        display: flex;
+                        flex-direction: row;
+                        justify-content: space-between;
+                    }
+                }
 
-    .cw-audio-range::-ms-track {
-        background: var(--dark-gray-color-20);
-        color: transparent;
-        height: 18px;
-        border: none;
+                .cw-recorder-visualization {
+                    display: flex;
+                    flex-direction: row;
+                    align-items: flex-end;
+                    gap: 2px;
+                    height: 28px;
+                    margin-bottom: 1em;
+                    padding-bottom: 4px;
+                    border-bottom: solid 2px var(--content-color-40);
+
+                    .cw-recorder-visualization-bar {
+                        min-height: 4px;
+                        width: calc(100% / 32);
+                        background-color: var(--base-color);
+
+                        &.idle-bar {
+                            height: 4px !important;
+                        }
+                    }
+                }
+                .cw-audio-buttons {
+                    display: flex;
+                    flex-direction: row;
+                    justify-content: space-evenly;
+
+                    button {
+                        cursor: pointer;
+                        border: none;
+                        background: transparent;
+                    }
+                }
+            }
+        }
     }
 
-    .cw-audio-range::-ms-tooltip {
-        display: none;
-    }
     .cw-audio-playlist-wrapper {
-        margin-top: -1em;
-        padding-top: 1em;
-        border: solid thin var(--content-color-40);
-        border-top: none;
-
-        &.empty {
-            border: none;
-        }
+        flex-grow: 1;
+        min-width: 270px;
+        max-height: 450px;
+        overflow-y: auto;
 
         .cw-audio-playlist {
             padding-left: 0;
+            margin-top: -1em;
             list-style: none;
             cursor: pointer;
 
-            &.with-recorder {
-                border-bottom: solid thin var(--content-color-40);
-            }
-
             li {
-                margin: 0 1em;
                 &:not(:last-child) {
                     border-bottom: solid thin var(--dark-gray-color-30);
                 }
 
                 .cw-playlist-item {
                     display: block;
-                    @include background-icon(file-audio2, clickable, 24);
-                    background-repeat: no-repeat;
-                    background-position: 1em center;
-
-                    margin: 1em 0;
-                    padding: 1em;
-                    padding-left: 4em;
-                    color: var(--base-color);
-                    &:hover {
-                        color: var(--active-color);
-                    }
-                    &.current-item {
-                        @include background-icon(play, clickable, 24);
-                        font-weight: 700;
-                        &.is-playing {
-                            @include background-icon(pause, clickable, 24);
-                        }
+                    padding: 1em 0;
+                    margin: 0;
+                    img {
+                        vertical-align: middle;
                     }
                 }
             }
         }
-        .cw-audio-playlist-recorder {
-            padding: 1em;
-        }
     }
-
-    .cw-audio-current-track {
-        @include background-icon(file-audio2, info, 96);
-        background-position: top center;
-        background-repeat: no-repeat;
-        width: 100%;
-        min-height: 140px;
-        margin: 1em 0 2em 0;
-        p {
-            text-align: center;
-            padding-top: 106px;
+}
+.edit-mp3-cover-wrapper {
+    display: flex;
+    flex-direction: row;
+    margin-bottom: 1em;
+
+    .edit-mp3-cover {
+        width: 128px;
+        height: 128px;
+        object-fit: cover;
+
+        &.default-cover {
+            padding: 32px;
+            border: solid thin var(--content-color-40);
         }
     }
-    .cw-audio-empty {
-        @include background-icon(file, info, 96);
-        border: solid thin var(--content-color-40);
-        background-position: center 1em;
-        background-repeat: no-repeat;
-        min-height: 140px;
-        padding: 1em;
-        p {
-            text-align: center;
-            padding-top: 106px;
-        }
+
+    .remove-cover {
+        background-color: transparent;
+        border: none;
+        height: 16px;
+        width: 16px;
+        padding: 0 8px;
+        cursor: pointer;
     }
 }
diff --git a/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss b/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss
index 24c57511cfd6b86bbab9226f09b43e2e21f3bd80..21e583e50a881a673e939b1e88d410a726aa1be7 100644
--- a/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss
+++ b/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss
@@ -131,4 +131,23 @@
         text-align: center;
         padding-top: 106px;
     }
+}
+
+.cw-call-to-action {
+    border: solid thin var(--content-color-40);
+    border-top: none;
+
+    button {
+        width: 100%;
+        background-color: var(--activity-color-20);
+        border: none;
+        text-align: left;
+        padding: 1em;
+        cursor: pointer;
+
+        img {
+            margin: 0 1em;
+            vertical-align: middle;
+        }
+    }
 }
\ No newline at end of file
diff --git a/resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue b/resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue
index e346d2096028241dd41aca812dbc23e3561a774f..9ca52b04dfaa6c514f304c11174dbc861a52e6f1 100644
--- a/resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue
@@ -19,106 +19,260 @@
                     @durationchange="setDuration"
                     @ended="onEndedListener"
                 />
-                <div v-if="!emptyAudio" class="cw-audio-container">
-                    <div class="cw-audio-current-track">
-                        <p>{{ activeTrackName }}</p>
+                <div class="cw-audio-container">
+                    <div
+                        v-if="!userRecorderEnabled"
+                        class="cw-audio-player"
+                        :class="{ 'with-playlist': playlistEnabled }"
+                    >
+                        <div class="cw-audio-cover" :class="{ loading: loadingCover, 'with-edit-button': canEditFile }">
+                            <img v-if="cover" :src="cover" class="cover" />
+                            <studip-icon
+                                v-else
+                                :shape="emptyAudio ? 'file' : 'file-audio'"
+                                :size="128"
+                                role="info"
+                                class="default-cover"
+                            />
+                            <button v-if="canEditFile" :title="$gettext('Bearbeiten')" @click="displayEditMP3">
+                                <studip-icon shape="edit" />
+                            </button>
+                        </div>
+                        <div class="cw-audio-controls-wrapper">
+                            <div class="cw-audio-current-track">
+                                <h2>{{ trackTitle }}</h2>
+                                <h3>{{ trackArtist }}</h3>
+                            </div>
+                            <div class="cw-audio-controls">
+                                <div class="cw-audio-progress">
+                                    <template v-if="!emptyAudio">
+                                        <input
+                                            class="cw-audio-range"
+                                            ref="range"
+                                            type="range"
+                                            :value="currentSeconds"
+                                            min="0"
+                                            :max="Math.round(durationSeconds)"
+                                            @input="rangeAction"
+                                        />
+                                        <p class="cw-audio-time">
+                                            <span>{{ currentTime }}</span>
+                                            <span>{{ durationTime }}</span>
+                                        </p>
+                                    </template>
+                                    <hr v-else />
+                                </div>
+                                <div class="cw-audio-buttons">
+                                    <button :title="$gettext('Zurück')" :disabled="!hasPlaylist" @click="prevAudio">
+                                        <studip-icon
+                                            shape="arr_eol-left"
+                                            :role="hasPlaylist ? 'clickable' : 'inactive'"
+                                            :size="24"
+                                        />
+                                    </button>
+                                    <button
+                                        v-if="!playing"
+                                        :title="$gettext('Abspielen')"
+                                        :disabled="emptyAudio"
+                                        @click="playAudio"
+                                    >
+                                        <studip-icon
+                                            shape="play"
+                                            :role="emptyAudio ? 'inactive' : 'clickable'"
+                                            :size="48"
+                                        />
+                                    </button>
+                                    <button v-else :title="$gettext('Pause')" @click="pauseAudio">
+                                        <studip-icon shape="pause" :size="48" />
+                                    </button>
+                                    <button :title="$gettext('Weiter')" :disabled="!hasPlaylist" @click="nextAudio">
+                                        <studip-icon
+                                            shape="arr_eol-right"
+                                            :role="hasPlaylist ? 'clickable' : 'inactive'"
+                                            :size="24"
+                                        />
+                                    </button>
+                                </div>
+                            </div>
+                        </div>
                     </div>
-                    <div class="cw-audio-controls">
-                        <input
-                            class="cw-audio-range"
-                            ref="range"
-                            type="range"
-                            :value="currentSeconds"
-                            min="0"
-                            :max="Math.round(durationSeconds)"
-                            @input="rangeAction"
-                        />
-                        <span class="cw-audio-time">{{ currentTime }} {{ durationTime ? '/ ' + durationTime : '' }}</span>
-
-                        <button v-if="hasPlaylist" class="cw-audio-button cw-audio-prevbutton" :title="$gettext('Zurück')" @click="prevAudio" />
-                        <button v-if="!playing" class="cw-audio-button cw-audio-playbutton" :title="$gettext('Abspielen')" @click="playAudio" />
-                        <button v-if="playing" class="cw-audio-button cw-audio-pausebutton" :title="$gettext('Pause')" @click="pauseAudio" />
-                        <button v-if="hasPlaylist" class="cw-audio-button cw-audio-nextbutton" :title="$gettext('Weiter')" @click="nextAudio" />
-                        <button class="cw-audio-button cw-audio-stopbutton" :title="$gettext('Anhalten')" @click="stopAudio" />
+                    <div v-else class="cw-audio-recorder with-playlist">
+                        <div class="cw-audio-cover">
+                            <studip-icon
+                                shape="microphone"
+                                :size="128"
+                                :role="isRecording ? 'status-red' : 'info'"
+                                class="default-cover"
+                            />
+                        </div>
+                        <div class="cw-audio-controls-wrapper">
+                            <div class="cw-audio-current-track">
+                                <h2>{{ $gettext('Aufnahme') }}</h2>
+                                <h3 v-if="isRecording">{{ $gettext('Aufnahme läuft') }}: {{ seconds2time(timer) }}</h3>
+                                <h3 v-if="newRecording && !isRecording">{{ seconds2time(timer) }}</h3>
+                            </div>
+                            <div class="cw-audio-controls">
+                                <div class="cw-recorder-visualization">
+                                    <div
+                                        v-for="(value, key) in recorderFrequencyData"
+                                        :key="'bar' + key"
+                                        :ref="'bar' + key"
+                                        class="cw-recorder-visualization-bar"
+                                        :class="{ 'idle-bar': !isRecording }"
+                                    ></div>
+                                </div>
+                                <div class="cw-audio-buttons">
+                                    <button
+                                        v-if="newRecording && !isRecording"
+                                        :title="$gettext('Aufnahme löschen')"
+                                        @click="resetRecorder"
+                                    >
+                                        <studip-icon shape="trash" :size="24" />
+                                    </button>
+                                    <button
+                                        v-if="!isRecording && !newRecording"
+                                        :title="$gettext('Neue Aufnahme starten')"
+                                        @click="startRecording"
+                                    >
+                                        <studip-icon shape="span-full" :size="48" role="status-red" />
+                                    </button>
+                                    <button
+                                        v-if="isRecording"
+                                        :title="$gettext('Aufnahme beenden')"
+                                        @click="stopRecording"
+                                    >
+                                        <studip-icon shape="stop" :size="48" />
+                                    </button>
+                                    <button
+                                        v-if="newRecording && !isRecording"
+                                        :title="$gettext('Aufnahme speichern')"
+                                        @click="storeRecording"
+                                    >
+                                        <studip-icon shape="download" :size="48" />
+                                    </button>
+                                    <button
+                                        v-if="newRecording && !isRecording"
+                                        :title="$gettext('Aufnahme wiederholen')"
+                                        @click="startRecording"
+                                    >
+                                        <studip-icon shape="span-full" :size="24" role="status-red" />
+                                    </button>
+                                </div>
+                            </div>
+                        </div>
                     </div>
-                </div>
-                <div v-if="emptyAudio" class="cw-audio-empty">
-                    <p>{{ $gettext('Es ist keine Audio-Datei verfügbar') }}</p>
-                </div>
-                <div v-show="currentSource === 'studip_folder'" class="cw-audio-playlist-wrapper" :class="[!showRecorder && emptyAudio ? 'empty' : '']">
-                    <ul v-show="hasPlaylist" class="cw-audio-playlist" :class="[showRecorder ? 'with-recorder' : '']">
-                        <li v-for="(file, index) in files" :key="file.id">
-                            <a
-                                :aria-current="(index === currentPlaylistItem) ? 'true' : 'false'"
-                                :class="{
-                                    'is-playing': index === currentPlaylistItem && playing,
-                                    'current-item': index === currentPlaylistItem,
-                                }"
-                                :title="$gettext('Audiodatei:') + ' ' + file.name"
-                                href="#"
-                                class="cw-playlist-item"
-                                @click.prevent="setCurrentPlaylistItem(index)"
-                            >
-                                {{ file.name }}
-                            </a>
-                        </li>
-                    </ul>
-                    <div v-if="showRecorder && canGetMediaDevices" class="cw-audio-playlist-recorder">
-                        <button 
-                            v-show="!userRecorderEnabled"
-                            class="button"
-                            :disabled="!folderSelected || folderLoadError"
-                            :title="enableRecorderTitle"
-                            @click="enableRecorder"
-                        >
-                            {{ $gettext('Aufnahme aktivieren') }}
-                        </button>
-                        <button
-                            v-show="userRecorderEnabled && !isRecording && !newRecording"
-                            class="button"
-                            @click="startRecording"
-                        >
-                            {{ $gettext('Aufnahme starten') }}
-                        </button>
-                        <button
-                            v-show="newRecording && !isRecording"
-                            class="button"
-                            @click="startRecording"
-                        >
-                            {{ $gettext('Aufnahme wiederholen') }}
-                        </button>
-                        <button 
-                            v-show="isRecording"
-                            class="button"
-                            @click="stopRecording"
-                        >
-                            {{ $gettext('Aufnahme beenden') }}
-                        </button>
-                        <button 
-                            v-show="newRecording && !isRecording"
-                            class="button"
-                            @click="resetRecorder"
-                        >
-                            {{ $gettext('Aufnahme löschen') }}
-                        </button>
-                        <button 
-                            v-show="newRecording && !isRecording"
-                            class="button"
-                            @click="storeRecording"
-                        >
-                            {{ $gettext('Aufnahme speichern') }}
-                        </button>
-                        <span v-show="isRecording">
-                            {{ $gettext('Aufnahme läuft') }}: {{seconds2time(timer)}}
-                        </span>
+                    <div v-show="playlistEnabled" class="cw-audio-playlist-wrapper">
+                        <ul class="cw-audio-playlist" :class="[showRecorder ? 'with-recorder' : '']">
+                            <li v-for="(file, index) in files" :key="file.id">
+                                <a
+                                    :aria-current="index === currentPlaylistItem ? 'true' : 'false'"
+                                    :title="$gettext('Audiodatei:') + ' ' + file.name"
+                                    href="#"
+                                    class="cw-playlist-item"
+                                    @click.prevent="setCurrentPlaylistItem(index)"
+                                >
+                                    <studip-icon
+                                        :shape="
+                                            index === currentPlaylistItem && !userRecorderEnabled
+                                                ? playing
+                                                    ? 'pause'
+                                                    : 'play'
+                                                : 'file-audio2'
+                                        "
+                                    />
+                                    {{ file.name }}
+                                </a>
+                            </li>
+                            <li v-if="emptyAudio">
+                                <p class="cw-playlist-item">
+                                    <studip-icon shape="file" role="info" />
+                                    {{ $gettext('Ordner enthält keine Audio-Dateien') }}
+                                </p>
+                            </li>
+                        </ul>
                     </div>
                 </div>
+                <div v-if="showRecorder && canGetMediaDevices" class="cw-call-to-action">
+                    <button
+                        v-if="!userRecorderEnabled"
+                        :title="enableRecorderTitle"
+                        @click.prevent="enableRecorder"
+                    >
+                        <studip-icon shape="microphone" :size="48"/>
+                        {{ $gettext('Aufnahme aktivieren') }}
+                    </button>
+                    <button v-else  @click.prevent="resetRecorder">
+                        <studip-icon shape="decline" :size="48"/>
+                        {{ $gettext('Aufnahme abbrechen') }}
+                    </button>
+                </div>
+                <studip-dialog
+                    v-if="showEditMP3"
+                    :title="$gettext('MP3 Metadaten bearbeiten')"
+                    :confirmText="$gettext('Speichern')"
+                    confirmClass="accept"
+                    :closeText="$gettext('Abbrechen')"
+                    closeClass="cancel"
+                    @close="closeEditMP3"
+                    @confirm="updateMP3"
+                    height="550"
+                    width="450"
+                >
+                    <template v-slot:dialogContent>
+                        <div class="edit-mp3-cover-wrapper">
+                            <img v-if="newCoverUrl" :src="newCoverUrl" class="edit-mp3-cover" />
+                            <template v-else>
+                                <template v-if="cover && !deleteCover">
+                                    <img :src="cover" class="edit-mp3-cover" />
+                                    <button
+                                        v-if="cover"
+                                        class="remove-cover"
+                                        :title="$gettext('Cover entfernen')"
+                                        @click="removeCover"
+                                    >
+                                        <studip-icon shape="trash" />
+                                    </button>
+                                </template>
+                                <studip-icon
+                                    v-if="cover === '' || deleteCover"
+                                    shape="file-audio"
+                                    :size="64"
+                                    role="info"
+                                    class="edit-mp3-cover default-cover"
+                                />
+                            </template>
+                        </div>
+                        <form class="default" @submit.prevent="">
+                            <label>
+                                {{ $gettext('Cover') }}
+                                <template v-if="!deleteCover">
+                                    <input
+                                        class="cw-file-input"
+                                        type="file"
+                                        ref="newCover"
+                                        accept="image/jpeg"
+                                        @change="updateCover"
+                                    />
+                                </template>
+                                <input v-else type="text" disabled :placeholder="$gettext('Cover wird entfernt')" />
+                            </label>
+                            <label>
+                                {{ $gettext('Titel') }}
+                                <input type="text" v-model="currentMP3Title" />
+                            </label>
+                            <label>
+                                {{ $gettext('Künstler') }}
+                                <input type="text" v-model="currentMP3Artist" />
+                            </label>
+                        </form>
+                    </template>
+                </studip-dialog>
             </template>
             <template v-if="canEdit" #edit>
                 <form class="default" @submit.prevent="">
                     <label>
                         {{ $gettext('Überschrift') }}
-                        <input type="text" v-model="currentTitle" />
+                        <input type="text" v-model="currentTitle" :placeholder="$gettext('optional')" />
                     </label>
                     <label>
                         {{ $gettext('Quelle') }}
@@ -168,6 +322,7 @@
 import BlockComponents from './block-components.js';
 import blockMixin from '@/vue/mixins/courseware/block.js';
 import { mapActions, mapGetters } from 'vuex';
+import MP3Tag from 'mp3tag.js';
 
 export default {
     name: 'courseware-audio-block',
@@ -197,7 +352,24 @@ export default {
             timer: 0,
             isRecording: false,
             newRecording: false,
-            folderLoadError: false
+            folderLoadError: false,
+            recorderAudioCtx: null,
+            recorderAnalyser: null,
+            recorderSource: null,
+            recorderBufferLength: 0,
+            recorderTimeData: null,
+            recorderFrequencyData: null,
+
+            mp3tag: null,
+            loadingCover: false,
+            volume: 100,
+
+            showEditMP3: false,
+            currentMP3Title: '',
+            currentMP3Artist: '',
+            imageBytes: null,
+            newCoverUrl: '',
+            deleteCover: false,
         };
     },
     computed: {
@@ -207,26 +379,33 @@ export default {
             urlHelper: 'urlHelper',
             userId: 'userId',
             usersById: 'users/byId',
-            relatedTermOfUse: 'terms-of-use/related'
+            relatedTermOfUse: 'terms-of-use/related',
         }),
         files() {
             const files =
                 this.relatedFileRefs({
                     parent: { type: 'folders', id: this.currentFolderId },
-                    relationship: 'file-refs'
+                    relationship: 'file-refs',
                 }) ?? [];
 
             return files
                 .filter((file) => {
-                    if (this.relatedTermOfUse({parent: file, relationship: 'terms-of-use'}).attributes['download-condition'] !== 0) {
+                    if (
+                        this.relatedTermOfUse({ parent: file, relationship: 'terms-of-use' }).attributes[
+                            'download-condition'
+                        ] !== 0
+                    ) {
                         return false;
-                    } 
-                    if (! file.attributes['mime-type'].includes('audio')) {
+                    }
+                    if (!file.attributes['mime-type'].includes('audio')) {
                         return false;
                     }
 
                     return true;
                 })
+                .sort((a, b) => {
+                    return new Date(a.attributes.mkdate) - new Date(b.attributes.mkdate);
+                })
                 .map(({ id, attributes }) => {
                     return {
                         id,
@@ -236,7 +415,8 @@ export default {
                             { type: 0, file_id: id, file_name: attributes.name },
                             true
                         ),
-                        mime_type: attributes['mime-type']
+                        mime_type: attributes['mime-type'],
+                        isRecording: attributes.description === 'CoursewareRecording',
                     };
                 });
         },
@@ -271,25 +451,36 @@ export default {
             return this.block?.attributes?.payload?.recorder_enabled;
         },
         showRecorder() {
-            return this.currentRecorderEnabled && this.currentSource === 'studip_folder';
+            return this.currentRecorderEnabled && this.playlistEnabled;
         },
         hasPlaylist() {
-            return this.files.length > 0 && this.currentSource === 'studip_folder';
+            return this.files.length > 0 && this.playlistEnabled;
+        },
+        playlistEnabled() {
+            return this.currentSource === 'studip_folder';
         },
         canGetMediaDevices() {
             return navigator.mediaDevices !== undefined;
         },
-        currentURL() {
+        activeFile() {
             if (this.currentSource === 'studip_file') {
-                return this.currentFile.download_url;
+                return this.currentFile;
             }
-            if (this.currentSource === 'studip_folder') {
+            if (this.playlistEnabled) {
                 if (this.files.length > 0) {
-                    return this.files[this.currentPlaylistItem].download_url;
-                } else {
-                    return '';
+                    return this.files[this.currentPlaylistItem];
                 }
             }
+
+            return null;
+        },
+        fileIsRecording() {
+            return this.activeFile?.isRecording ?? false;
+        },
+        currentURL() {
+            if (this.activeFile) {
+                return this.activeFile.download_url;
+            }
             if (this.currentSource === 'web') {
                 return this.currentWebUrl;
             }
@@ -297,15 +488,8 @@ export default {
             return '';
         },
         activeTrackName() {
-            if (this.currentSource === 'studip_file') {
-                return this.currentFile.name;
-            }
-            if (this.currentSource === 'studip_folder') {
-                if (this.files.length > 0) {
-                    return this.files[this.currentPlaylistItem].name;
-                } else {
-                    return '';
-                }
+            if (this.activeFile) {
+                return this.activeFile.name;
             }
             if (this.currentSource === 'web') {
                 return this.currentWebUrl;
@@ -313,6 +497,19 @@ export default {
 
             return '';
         },
+        trackTitle() {
+            if (this.emptyAudio) {
+                return this.$gettext('Es ist keine Audio-Datei verfügbar');
+            }
+            if (this.tags && this.tags.title !== '') {
+                return this.tags.title;
+            }
+
+            return this.activeTrackName;
+        },
+        trackArtist() {
+            return this.tags?.artist ?? '';
+        },
         emptyAudio() {
             if (this.currentSource === 'studip_folder' && this.currentFolderId !== '' && this.files.length > 0) {
                 return false;
@@ -335,21 +532,69 @@ export default {
             }
 
             return this.$gettext('Aktiviert die Aufnahmefunktion');
-        }
+        },
+        tags() {
+            return this.mp3tag?.tags ?? {};
+        },
+        hasMP3Tags() {
+            return Object.keys(this.tags).length > 0;
+        },
+        cover() {
+            const image = this.tags?.v2?.APIC?.[0];
+            if (image) {
+                return this.imageURL(image.data, image.format);
+            }
+
+            if (this.fileIsRecording) {
+                const ownerId = this.activeFileRef?.relationships?.owner?.data?.id;
+                if (ownerId) {
+                    const owner = this.usersById({ id: ownerId });
+                    return owner?.meta?.avatar?.normal ?? '';
+                }
+            }
+
+            return '';
+        },
+        activeFileRef() {
+            return this.fileRefById({ id: this.activeFile.id });
+        },
+        canEditFile() {
+            return this.hasMP3Tags && this.activeFileRef.attributes['is-editable'];
+        },
     },
-    mounted() {
+    async mounted() {
         this.initCurrentData();
     },
     methods: {
         ...mapActions({
             loadFileRef: 'file-refs/loadById',
             loadRelatedFileRefs: 'file-refs/loadRelated',
+            updateFileRefs: 'file-refs/update',
             updateBlock: 'updateBlockInContainer',
             companionWarning: 'companionWarning',
             companionSuccess: 'companionSuccess',
             companionError: 'companionError',
             createFile: 'createFile',
+            updateFileContent: 'updateFileContent',
+            loadUser: 'users/loadById',
         }),
+
+        toDataURL(url) {
+            return new Promise(function (resolve, reject) {
+                var xhr = new XMLHttpRequest();
+                xhr.onload = function () {
+                    var reader = new FileReader();
+                    reader.onloadend = function () {
+                        resolve(reader.result);
+                    };
+                    reader.readAsArrayBuffer(xhr.response);
+                };
+                xhr.open('GET', url);
+                xhr.responseType = 'blob';
+                xhr.send();
+            });
+        },
+
         initCurrentData() {
             this.currentTitle = this.title;
             this.currentSource = this.source;
@@ -370,9 +615,9 @@ export default {
                 await this.loadRelatedFileRefs({
                     parent: { type: 'folders', id: this.currentFolderId },
                     relationship: 'file-refs',
-                    options: { include: 'terms-of-use' }
+                    options: { include: 'terms-of-use' },
                 });
-            } catch(error) {
+            } catch (error) {
                 this.folderLoadError = true;
             }
         },
@@ -388,7 +633,7 @@ export default {
             if (this.currentSource === 'studip_file') {
                 if (this.currentFileId === '') {
                     this.companionWarning({
-                        info: this.$gettext('Bitte wählen Sie eine Datei aus.')
+                        info: this.$gettext('Bitte wählen Sie eine Datei aus.'),
                     });
                     return false;
                 }
@@ -398,7 +643,7 @@ export default {
             } else if (this.currentSource === 'studip_folder') {
                 if (this.currentFolderId === '') {
                     this.companionWarning({
-                        info: this.$gettext('Bitte wählen Sie einen Ordner aus.')
+                        info: this.$gettext('Bitte wählen Sie einen Ordner aus.'),
                     });
                     return false;
                 }
@@ -419,8 +664,11 @@ export default {
                 this.$refs.audio.currentTime = this.$refs.range.value;
             }
         },
+        setVolume() {
+            this.$refs.audio.volume = this.volume / 100;
+        },
         setDuration() {
-            let duration = this.$refs.audio.duration
+            let duration = this.$refs.audio.duration;
             if (!isNaN(duration) && isFinite(duration)) {
                 this.durationSeconds = duration;
             } else {
@@ -441,9 +689,9 @@ export default {
                 this.playing = true;
             } else {
                 this.companionError({
-                    info: this.$gettext('Ihr Browser unterstützt dieses Audioformat leider nicht.')
+                    info: this.$gettext('Ihr Browser unterstützt dieses Audioformat leider nicht.'),
                 });
-                if(this.hasPlaylist) {
+                if (this.hasPlaylist) {
                     this.nextAudio();
                 }
             }
@@ -461,7 +709,7 @@ export default {
         },
         onEndedListener() {
             this.stopAudio();
-            if(this.hasPlaylist) {
+            if (this.hasPlaylist) {
                 this.nextAudio();
             }
         },
@@ -486,6 +734,7 @@ export default {
             return time;
         },
         setCurrentPlaylistItem(index) {
+            this.userRecorderEnabled = false;
             if (this.currentPlaylistItem === index) {
                 if (this.playing) {
                     this.pauseAudio();
@@ -494,7 +743,7 @@ export default {
                 }
             } else {
                 this.currentPlaylistItem = index;
-                this.$nextTick(()=> {
+                this.$nextTick(() => {
                     this.playAudio();
                 });
             }
@@ -506,7 +755,7 @@ export default {
             } else {
                 this.currentPlaylistItem = this.files.length - 1;
             }
-            this.$nextTick(()=> {
+            this.$nextTick(() => {
                 this.playAudio();
             });
         },
@@ -514,7 +763,7 @@ export default {
             this.stopAudio();
             if (this.currentPlaylistItem < this.files.length - 1) {
                 this.currentPlaylistItem = this.currentPlaylistItem + 1;
-                this.$nextTick(()=> {
+                this.$nextTick(() => {
                     this.playAudio();
                 });
             }
@@ -534,26 +783,59 @@ export default {
                         { type: 0, file_id: fileRef.id, file_name: fileRef.attributes.name },
                         true
                     ),
-                    mime_type: fileRef.attributes['mime-type']
+                    mime_type: fileRef.attributes['mime-type'],
                 });
             }
         },
+        async loadTags() {
+            this.mp3tag = null;
+            let view = this;
+            let response = await fetch(this.currentURL);
+            let data = await response.blob();
+            let file = new File([data], this.activeTrackName);
+
+            let reader = new FileReader();
+            reader.onload = function () {
+                const buffer = this.result;
+                view.mp3tag = new MP3Tag(buffer);
+                view.mp3tag.read();
+            };
+
+            reader.readAsArrayBuffer(file);
+        },
+        imageURL(bytes, format) {
+            let encoded = '';
+            bytes.forEach(function (byte) {
+                encoded += String.fromCharCode(byte);
+            });
+
+            return `data:${format};base64,${btoa(encoded)}`;
+        },
         enableRecorder() {
             if (!this.folderSelected || this.folderLoadError) {
                 return false;
             }
             let view = this;
-            navigator.mediaDevices.getUserMedia({ audio: true })
-                .then(function(stream) {
-                    view.recorder = new MediaRecorder(stream, {type: 'audio/webm; codecs:vp9' });
+            navigator.mediaDevices
+                .getUserMedia({ audio: true })
+                .then(function (stream) {
+                    view.recorder = new MediaRecorder(stream, { type: 'audio/webm; codecs:vp9' });
                     view.userRecorderEnabled = true;
-                    view.recorder.ondataavailable = e => {
+                    view.recorder.ondataavailable = (e) => {
                         view.chunks.push(e.data);
                     };
+
+                    view.recorderAudioCtx = new AudioContext();
+                    view.recorderAnalyser = view.recorderAudioCtx.createAnalyser();
+                    view.recorderSource = view.recorderAudioCtx.createMediaStreamSource(stream);
+                    view.recorderSource.connect(view.recorderAnalyser);
+                    view.recorderAnalyser.fftSize = 2 ** 6;
+                    view.recorderBufferLength = view.recorderAnalyser.frequencyBinCount;
+                    view.recorderFrequencyData = new Uint8Array(view.recorderBufferLength);
                 })
                 .catch(() => {
                     view.companionWarning({
-                        info: view.$gettext('Sie müssen ein Mikrofon freigeben, um eine Aufnahme starten zu können.')
+                        info: view.$gettext('Sie müssen ein Mikrofon freigeben, um eine Aufnahme starten zu können.'),
                     });
                 });
         },
@@ -563,7 +845,9 @@ export default {
             this.timer = 0;
             this.recorder.start();
             this.isRecording = true;
-            setTimeout(function(){ view.setTimer(); }, 1000);
+            setTimeout(function () {
+                view.setTimer();
+            }, 1000);
         },
         stopRecording() {
             this.isRecording = false;
@@ -574,48 +858,136 @@ export default {
             let view = this;
             if (this.recorder.state === 'recording') {
                 this.timer++;
-                setTimeout(function(){ view.setTimer(); }, 1000);
+                setTimeout(function () {
+                    view.setTimer();
+                }, 1000);
+            }
+        },
+        recorderDrawTimeData() {
+            this.recorderAnalyser.getByteFrequencyData(this.recorderFrequencyData);
+
+            for (let i = 0; i < this.recorderFrequencyData.length; i++) {
+                let ref = 'bar' + i;
+                this.$refs[ref][0].style.height = (this.recorderFrequencyData[i] / 255) * 28 + 'px';
+            }
+
+            if (this.isRecording) {
+                let view = this;
+                requestAnimationFrame(() => view.recorderDrawTimeData());
             }
         },
         async storeRecording() {
             let view = this;
-            let user = this.usersById({id: this.userId});
-            let blob = new Blob(view.chunks, {type: 'audio/webm; codecs:vp9' });
+            let user = this.usersById({ id: this.userId });
+            let blob = new Blob(view.chunks, { type: 'audio/webm; codecs:vp9' });
+
             let file = {
                 attributes: {
-                    name: (user.attributes["formatted-name"]).replace(/\s+/g, '_') + '.webm'
+                    name: user.attributes['formatted-name'].replace(/\s+/g, '_') + '.webm',
                 },
                 relationships: {
                     'terms-of-use': {
                         data: {
-                            id: 'SELFMADE_NONPUB'
-                        }
-                    }
-                }
+                            id: 'SELFMADE_NONPUB',
+                        },
+                    },
+                },
             };
             let fileObj = await this.createFile({
                 file: file,
                 filedata: blob,
-                folder: {id: this.currentFolderId}
+                folder: { id: this.currentFolderId },
             });
-            if(fileObj && fileObj.type === 'file-refs') {
+            if (fileObj && fileObj.type === 'file-refs') {
                 this.companionSuccess({
-                    info: this.$gettext('Die Aufnahme wurde erfolgreich im Dateibereich abgelegt.')
+                    info: this.$gettext('Die Aufnahme wurde erfolgreich im Dateibereich abgelegt.'),
                 });
+                fileObj.attributes.description = 'CoursewareRecording';
+                await this.updateFileRefs(fileObj);
             } else {
                 this.companionError({
-                    info: this.$gettext('Es ist ein Fehler aufgetreten! Die Aufnahme konnte nicht gespeichert werden.')
+                    info: this.$gettext('Es ist ein Fehler aufgetreten! Die Aufnahme konnte nicht gespeichert werden.'),
                 });
             }
             this.newRecording = false;
+            this.userRecorderEnabled = false;
             this.getFolderFiles();
         },
         resetRecorder() {
+            this.userRecorderEnabled = false;
+            this.isRecording = false;
             this.newRecording = false;
             this.chunks = [];
             this.timer = 0;
             this.blob = null;
         },
+        displayEditMP3() {
+            this.stopAudio();
+            this.currentMP3Title = this.tags.title;
+            this.currentMP3Artist = this.tags.artist;
+            this.showEditMP3 = true;
+        },
+        closeEditMP3() {
+            this.showEditMP3 = false;
+            this.currentMP3Title = '';
+            this.currentMP3Artist = '';
+            this.imageBytes = null;
+            this.newCoverUrl = '';
+            this.deleteCover = false;
+        },
+        removeCover() {
+            this.deleteCover = true;
+            this.$refs.newCover.value = '';
+        },
+        async updateCover() {
+            this.deleteCover = false;
+            const file = this.$refs?.newCover?.files[0];
+            const buffer = await this.readFile(file);
+            this.imageBytes = new Uint8Array(buffer);
+            this.newCoverUrl = this.imageURL(this.imageBytes, 'image/jpeg');
+        },
+        readFile(file) {
+            return new Promise(function (resolve, reject) {
+                const reader = new FileReader();
+                reader.onload = () => {
+                    resolve(reader.result);
+                };
+                reader.onerror = reject;
+                reader.readAsArrayBuffer(file);
+            });
+        },
+        async updateMP3() {
+            this.mp3tag.tags.title = this.currentMP3Title;
+            this.mp3tag.tags.artist = this.currentMP3Artist;
+
+            if (this.imageBytes) {
+                this.mp3tag.tags.v2.APIC = [
+                    {
+                        format: 'image/jpeg',
+                        type: 3,
+                        description: '',
+                        data: this.imageBytes,
+                    },
+                ];
+            }
+            if (this.deleteCover) {
+                this.mp3tag.tags.v2.APIC = [];
+            }
+            this.mp3tag.save();
+            const modifiedFile = new File([this.mp3tag.buffer], this.activeTrackName, {
+                type: 'audio/mpeg',
+            });
+
+            const fileRef = await this.fileRefById({ id: this.activeFile.id });
+
+            let fileObj = await this.updateFileContent({
+                file: fileRef,
+                filedata: modifiedFile,
+            });
+
+            this.closeEditMP3();
+            this.getFolderFiles();
+        },
     },
     watch: {
         currentFolderId(newState) {
@@ -625,9 +997,27 @@ export default {
                 this.getFolderFiles();
             }
         },
+        currentURL() {
+            this.loadingCover = true;
+            this.loadTags();
+            if (this.fileIsRecording) {
+                const ownerId = this.activeFileRef?.relationships?.owner?.data?.id;
+                if (ownerId) {
+                    this.loadUser({ id: ownerId });
+                }
+            }
+            setTimeout(() => {
+                this.loadingCover = false;
+            }, 200);
+        },
+        isRecording(newState) {
+            if (newState) {
+                this.recorderDrawTimeData();
+            }
+        },
     },
 };
 </script>
 <style scoped lang="scss">
-    @import "../../../../assets/stylesheets/scss/courseware/blocks/audio.scss";
-</style>
\ No newline at end of file
+@import '../../../../assets/stylesheets/scss/courseware/blocks/audio.scss';
+</style>
diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js
index 89d7da82d7ecdd27f0f8f0ae571e2443bf610fb5..f5bb68b6e78c346e50033b46b861ccb8273dc27e 100644
--- a/resources/vue/store/courseware/courseware.module.js
+++ b/resources/vue/store/courseware/courseware.module.js
@@ -360,6 +360,23 @@ export const actions = {
         return response ? response.data.data : response;
     },
 
+    async updateFileContent(context, { file, filedata }) {
+        const url = `file-refs/${file.id}/content`;
+        const formData = new FormData();
+        formData.append('file', filedata, file.attributes.name);
+        let request = await state.httpClient.post(url, formData);
+        let response = null;
+        try {
+            response = await state.httpClient.get(request.headers.location);
+        }
+        catch(e) {
+            console.debug(e);
+            response = null;
+        }
+
+        return response ? response.data.data : response;
+    },
+
     async createRootFolder({ dispatch, rootGetters }, { context, folder }) {
         // get root folder for this context
         await dispatch(