diff options
Diffstat (limited to 'resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue')
| -rw-r--r-- | resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue | 688 |
1 files changed, 539 insertions, 149 deletions
diff --git a/resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue b/resources/vue/components/courseware/blocks/CoursewareAudioBlock.vue index e346d20..9ca52b0 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> |
