Move Waveform editor to Vue 3 style.
This commit is contained in:
parent
3ed6a966b1
commit
59cbfbfc66
|
@ -8,8 +8,8 @@
|
|||
</div>
|
||||
</b-form-group>
|
||||
</b-row>
|
||||
<b-row class="mt-3">
|
||||
<b-col md="8">
|
||||
<b-row class="mt-3 align-items-center">
|
||||
<b-col md="7">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0">
|
||||
<label for="waveform-zoom">
|
||||
|
@ -17,26 +17,27 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="flex-fill mx-3">
|
||||
<b-form-input id="waveform-zoom" v-model="zoom" type="range" min="0" max="256" class="w-100"></b-form-input>
|
||||
<b-form-input id="waveform-zoom" v-model.number="zoom" type="range" min="0" max="256"
|
||||
class="w-100"></b-form-input>
|
||||
</div>
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col md="4">
|
||||
<b-col md="5">
|
||||
<div class="inline-volume-controls d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<a class="btn btn-sm btn-outline-inverse py-0 px-3" href="#" @click.prevent="volume = 0">
|
||||
<a class="btn btn-sm btn-outline-inverse" href="#" @click.prevent="volume = 0"
|
||||
:title="$gettext('Mute')">
|
||||
<icon icon="volume_mute"></icon>
|
||||
{{ $gettext('Mute') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-fill mx-1">
|
||||
<input type="range" :title="langVolume" class="player-volume-range custom-range w-100" min="0" max="100"
|
||||
step="1" v-model="volume">
|
||||
<input type="range" :title="$gettext('Volume')" class="player-volume-range custom-range w-100"
|
||||
min="0" max="100" step="1" v-model.number="volume">
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<a class="btn btn-sm btn-outline-inverse py-0 px-3" href="#" @click.prevent="volume = 100">
|
||||
<a class="btn btn-sm btn-outline-inverse" href="#" @click.prevent="volume = 100"
|
||||
:title="$gettext('Full Volume')">
|
||||
<icon icon="volume_up"></icon>
|
||||
{{ $gettext('Full Volume') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -45,128 +46,119 @@
|
|||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaveSurfer from 'wavesurfer.js';
|
||||
<script setup>
|
||||
import WS from 'wavesurfer.js';
|
||||
import timeline from 'wavesurfer.js/dist/plugin/wavesurfer.timeline.js';
|
||||
import regions from 'wavesurfer.js/dist/plugin/wavesurfer.regions.js';
|
||||
import getLogarithmicVolume from '~/functions/getLogarithmicVolume.js';
|
||||
import store from 'store';
|
||||
import Icon from './Icon';
|
||||
import {useStorage} from "@vueuse/core";
|
||||
import {onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
|
||||
export default {
|
||||
name: 'Waveform',
|
||||
components: { Icon },
|
||||
props: {
|
||||
audioUrl: String,
|
||||
waveformUrl: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
wavesurfer: null,
|
||||
zoom: 0,
|
||||
volume: 0
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.wavesurfer = WaveSurfer.create({
|
||||
backend: 'MediaElement',
|
||||
container: '#waveform',
|
||||
waveColor: '#2196f3',
|
||||
progressColor: '#4081CF',
|
||||
plugins: [
|
||||
timeline.create({
|
||||
container: '#waveform-timeline',
|
||||
primaryColor: '#222',
|
||||
secondaryColor: '#888',
|
||||
primaryFontColor: '#222',
|
||||
secondaryFontColor: '#888'
|
||||
}),
|
||||
regions.create({
|
||||
regions: []
|
||||
})
|
||||
]
|
||||
});
|
||||
const props = defineProps({
|
||||
audioUrl: String,
|
||||
waveformUrl: String
|
||||
});
|
||||
|
||||
this.wavesurfer.on('ready', () => {
|
||||
this.$emit('ready');
|
||||
});
|
||||
const emit = defineEmits(['ready']);
|
||||
|
||||
this.axios.get(this.waveformUrl).then((resp) => {
|
||||
let waveform = resp.data;
|
||||
if (waveform.data) {
|
||||
this.wavesurfer.load(this.audioUrl, waveform.data);
|
||||
} else {
|
||||
this.wavesurfer.load(this.audioUrl);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
this.wavesurfer.load(this.audioUrl);
|
||||
});
|
||||
let wavesurfer = null;
|
||||
|
||||
// Check webstorage for existing volume preference.
|
||||
if (store.enabled && store.get('player_volume') !== undefined) {
|
||||
this.volume = store.get('player_volume', 55);
|
||||
const volume = useStorage('player_volume', 55);
|
||||
const zoom = ref(0);
|
||||
|
||||
watch(zoom, (val) => {
|
||||
wavesurfer?.zoom(val);
|
||||
});
|
||||
|
||||
watch(volume, (val) => {
|
||||
wavesurfer?.setVolume(getLogarithmicVolume(val));
|
||||
});
|
||||
|
||||
const {axios} = useAxios();
|
||||
|
||||
onMounted(() => {
|
||||
wavesurfer = WS.create({
|
||||
backend: 'MediaElement',
|
||||
container: '#waveform',
|
||||
waveColor: '#2196f3',
|
||||
progressColor: '#4081CF',
|
||||
plugins: [
|
||||
timeline.create({
|
||||
container: '#waveform-timeline',
|
||||
primaryColor: '#222',
|
||||
secondaryColor: '#888',
|
||||
primaryFontColor: '#222',
|
||||
secondaryFontColor: '#888'
|
||||
}),
|
||||
regions.create({
|
||||
regions: []
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
wavesurfer.on('ready', () => {
|
||||
wavesurfer.setVolume(getLogarithmicVolume(volume.value));
|
||||
|
||||
emit('ready');
|
||||
});
|
||||
|
||||
axios.get(props.waveformUrl).then((resp) => {
|
||||
let waveformJson = resp?.data?.data ?? null;
|
||||
if (waveformJson) {
|
||||
wavesurfer.load(props.audioUrl, waveformJson);
|
||||
} else {
|
||||
wavesurfer.load(props.audioUrl);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
langVolume () {
|
||||
return this.$gettext('Volume');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
play () {
|
||||
if (this.wavesurfer) {
|
||||
this.wavesurfer.play();
|
||||
}
|
||||
},
|
||||
stop () {
|
||||
if (this.wavesurfer) {
|
||||
this.wavesurfer.pause();
|
||||
}
|
||||
},
|
||||
getCurrentTime () {
|
||||
if (this.wavesurfer) {
|
||||
return this.wavesurfer.getCurrentTime();
|
||||
}
|
||||
},
|
||||
getDuration () {
|
||||
if (this.wavesurfer) {
|
||||
return this.wavesurfer.getDuration();
|
||||
}
|
||||
},
|
||||
addRegion (start, end, color) {
|
||||
if (this.wavesurfer) {
|
||||
this.wavesurfer.addRegion(
|
||||
{
|
||||
start: start,
|
||||
end: end,
|
||||
resize: false,
|
||||
drag: false,
|
||||
color: color
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
clearRegions () {
|
||||
if (this.wavesurfer) {
|
||||
this.wavesurfer.clearRegions();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
zoom: function (val) {
|
||||
this.wavesurfer.zoom(Number(val));
|
||||
},
|
||||
volume: function (volume) {
|
||||
this.wavesurfer.setVolume(getLogarithmicVolume(volume));
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
wavesurfer.load(props.audioUrl);
|
||||
});
|
||||
});
|
||||
|
||||
if (store.enabled) {
|
||||
store.set('player_volume', volume);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.wavesurfer = null;
|
||||
}
|
||||
onUnmounted(() => {
|
||||
wavesurfer = null;
|
||||
});
|
||||
|
||||
const play = () => {
|
||||
wavesurfer?.play();
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
wavesurfer?.pause();
|
||||
};
|
||||
|
||||
const getCurrentTime = () => {
|
||||
return wavesurfer?.getCurrentTime();
|
||||
};
|
||||
|
||||
const getDuration = () => {
|
||||
return wavesurfer?.getDuration();
|
||||
}
|
||||
|
||||
const addRegion = (start, end, color) => {
|
||||
wavesurfer?.addRegion(
|
||||
{
|
||||
start: start,
|
||||
end: end,
|
||||
resize: false,
|
||||
drag: false,
|
||||
color: color
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const clearRegions = () => {
|
||||
wavesurfer?.clearRegions();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
play,
|
||||
stop,
|
||||
getCurrentTime,
|
||||
getDuration,
|
||||
addRegion,
|
||||
clearRegions
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="v$.form.$invalid"
|
||||
@submit="doEdit" @hidden="clearContents">
|
||||
<modal-form ref="modal" :loading="loading" :title="$gettext('Edit Media')" :error="error"
|
||||
:disable-save-button="v$.$invalid"
|
||||
@submit="doEdit" @hidden="resetForm">
|
||||
|
||||
<b-tabs content-class="mt-3" pills>
|
||||
<b-tab active>
|
||||
|
@ -8,14 +9,14 @@
|
|||
{{ $gettext('Basic Information') }}
|
||||
</template>
|
||||
|
||||
<media-form-basic-info :form="v$.form"></media-form-basic-info>
|
||||
<media-form-basic-info :form="v$"></media-form-basic-info>
|
||||
</b-tab>
|
||||
<b-tab>
|
||||
<template #title>
|
||||
{{ $gettext('Playlists') }}
|
||||
</template>
|
||||
|
||||
<media-form-playlists :form="v$.form" :playlists="playlists"></media-form-playlists>
|
||||
<media-form-playlists :form="v$" :playlists="playlists"></media-form-playlists>
|
||||
</b-tab>
|
||||
<b-tab lazy>
|
||||
<template #title>
|
||||
|
@ -30,7 +31,7 @@
|
|||
{{ $gettext('Custom Fields') }}
|
||||
</template>
|
||||
|
||||
<media-form-custom-fields :form="v$.form" :custom-fields="customFields"></media-form-custom-fields>
|
||||
<media-form-custom-fields :form="v$" :custom-fields="customFields"></media-form-custom-fields>
|
||||
</b-tab>
|
||||
|
||||
<b-tab lazy>
|
||||
|
@ -47,14 +48,15 @@
|
|||
{{ $gettext('Advanced') }}
|
||||
</template>
|
||||
|
||||
<media-form-advanced-settings :form="v$.form" :song-length="songLength"></media-form-advanced-settings>
|
||||
<media-form-advanced-settings :form="v$" :song-length="songLength"></media-form-advanced-settings>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</modal-form>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
<script setup>
|
||||
import {required} from '@vuelidate/validators';
|
||||
import _ from 'lodash';
|
||||
import {defaultTo, forEach, map} from 'lodash';
|
||||
import MediaFormBasicInfo from './Form/BasicInfo';
|
||||
import MediaFormAlbumArt from './Form/AlbumArt';
|
||||
import MediaFormCustomFields from './Form/CustomFields';
|
||||
|
@ -62,173 +64,161 @@ import MediaFormAdvancedSettings from './Form/AdvancedSettings';
|
|||
import MediaFormPlaylists from './Form/Playlists';
|
||||
import MediaFormWaveformEditor from './Form/WaveformEditor';
|
||||
import ModalForm from "~/components/Common/ModalForm";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
import {ref} from "vue";
|
||||
import {useVuelidateOnForm} from "~/functions/useVuelidateOnForm";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
import {useNotify} from "~/vendor/bootstrapVue";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
components: {
|
||||
ModalForm,
|
||||
MediaFormPlaylists,
|
||||
MediaFormWaveformEditor,
|
||||
MediaFormAdvancedSettings,
|
||||
MediaFormCustomFields,
|
||||
MediaFormAlbumArt,
|
||||
MediaFormBasicInfo
|
||||
},
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
props: {
|
||||
customFields: Array,
|
||||
playlists: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
recordUrl: null,
|
||||
error: null,
|
||||
albumArtUrl: null,
|
||||
waveformUrl: null,
|
||||
audioUrl: null,
|
||||
songLength: null,
|
||||
form: {}
|
||||
};
|
||||
},
|
||||
validations() {
|
||||
let validations = {
|
||||
form: {
|
||||
path: {
|
||||
required
|
||||
},
|
||||
title: {},
|
||||
artist: {},
|
||||
album: {},
|
||||
genre: {},
|
||||
lyrics: {},
|
||||
isrc: {},
|
||||
art: {},
|
||||
amplify: {},
|
||||
fade_overlap: {},
|
||||
fade_in: {},
|
||||
fade_out: {},
|
||||
cue_in: {},
|
||||
cue_out: {},
|
||||
playlists: {},
|
||||
custom_fields: {}
|
||||
}
|
||||
const props = defineProps({
|
||||
customFields: Array,
|
||||
playlists: Array
|
||||
});
|
||||
|
||||
const emit = defineEmits(['relist']);
|
||||
|
||||
const loading = ref(true);
|
||||
const error = ref(null);
|
||||
const recordUrl = ref('');
|
||||
const albumArtUrl = ref('');
|
||||
const waveformUrl = ref('');
|
||||
const audioUrl = ref('');
|
||||
const songLength = ref(0);
|
||||
|
||||
const buildForm = () => {
|
||||
let blankForm = {
|
||||
path: null,
|
||||
title: null,
|
||||
artist: null,
|
||||
album: null,
|
||||
genre: null,
|
||||
lyrics: null,
|
||||
isrc: null,
|
||||
amplify: null,
|
||||
fade_overlap: null,
|
||||
fade_in: null,
|
||||
fade_out: null,
|
||||
cue_in: null,
|
||||
cue_out: null,
|
||||
playlists: [],
|
||||
custom_fields: {}
|
||||
};
|
||||
|
||||
let validations = {
|
||||
path: {required},
|
||||
title: {},
|
||||
artist: {},
|
||||
album: {},
|
||||
genre: {},
|
||||
lyrics: {},
|
||||
isrc: {},
|
||||
art: {},
|
||||
amplify: {},
|
||||
fade_overlap: {},
|
||||
fade_in: {},
|
||||
fade_out: {},
|
||||
cue_in: {},
|
||||
cue_out: {},
|
||||
playlists: {},
|
||||
custom_fields: {}
|
||||
};
|
||||
|
||||
forEach(props.customFields.slice(), (field) => {
|
||||
validations.custom_fields[field.short_name] = {};
|
||||
blankForm.custom_fields[field.short_name] = null;
|
||||
});
|
||||
|
||||
return {blankForm, validations};
|
||||
};
|
||||
|
||||
const {blankForm, validations} = buildForm();
|
||||
const {form, resetForm: resetBaseForm, v$} = useVuelidateOnForm(validations, blankForm);
|
||||
|
||||
const resetForm = () => {
|
||||
resetBaseForm();
|
||||
|
||||
loading.value = false;
|
||||
error.value = null;
|
||||
|
||||
albumArtUrl.value = '';
|
||||
waveformUrl.value = '';
|
||||
recordUrl.value = '';
|
||||
audioUrl.value = '';
|
||||
};
|
||||
|
||||
const modal = ref(); // BModal
|
||||
|
||||
const close = () => {
|
||||
modal.value?.hide();
|
||||
};
|
||||
|
||||
const {axios} = useAxios();
|
||||
|
||||
const open = (newRecordUrl, newAlbumArtUrl, newAudioUrl, newWaveformUrl) => {
|
||||
resetForm();
|
||||
|
||||
loading.value = true;
|
||||
recordUrl.value = newRecordUrl;
|
||||
albumArtUrl.value = newAlbumArtUrl;
|
||||
audioUrl.value = newAudioUrl;
|
||||
waveformUrl.value = newWaveformUrl;
|
||||
|
||||
modal.value?.show();
|
||||
|
||||
axios.get(newRecordUrl).then((resp) => {
|
||||
let d = resp.data;
|
||||
|
||||
songLength.value = d.length_text;
|
||||
|
||||
let newForm = {
|
||||
path: d.path,
|
||||
title: d.title,
|
||||
artist: d.artist,
|
||||
album: d.album,
|
||||
genre: d.genre,
|
||||
lyrics: d.lyrics,
|
||||
isrc: d.isrc,
|
||||
amplify: d.amplify,
|
||||
fade_overlap: d.fade_overlap,
|
||||
fade_in: d.fade_in,
|
||||
fade_out: d.fade_out,
|
||||
cue_in: d.cue_in,
|
||||
cue_out: d.cue_out,
|
||||
playlists: map(d.playlists, 'id'),
|
||||
custom_fields: {}
|
||||
};
|
||||
|
||||
_.forEach(this.customFields.slice(), (field) => {
|
||||
validations.form.custom_fields[field.short_name] = {};
|
||||
forEach(props.customFields.slice(), (field) => {
|
||||
newForm.custom_fields[field.short_name] = defaultTo(d.custom_fields[field.short_name], null);
|
||||
});
|
||||
|
||||
return validations;
|
||||
},
|
||||
computed: {
|
||||
langTitle() {
|
||||
return this.$gettext('Edit Media');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetForm() {
|
||||
this.loading = false;
|
||||
this.error = null;
|
||||
|
||||
this.albumArtUrl = null;
|
||||
this.waveformUrl = null;
|
||||
this.recordUrl = null;
|
||||
this.audioUrl = null;
|
||||
|
||||
let customFields = {};
|
||||
_.forEach(this.customFields.slice(), (field) => {
|
||||
customFields[field.short_name] = null;
|
||||
});
|
||||
|
||||
this.form = {
|
||||
path: null,
|
||||
title: null,
|
||||
artist: null,
|
||||
album: null,
|
||||
genre: null,
|
||||
lyrics: null,
|
||||
isrc: null,
|
||||
amplify: null,
|
||||
fade_overlap: null,
|
||||
fade_in: null,
|
||||
fade_out: null,
|
||||
cue_in: null,
|
||||
cue_out: null,
|
||||
playlists: [],
|
||||
custom_fields: customFields
|
||||
};
|
||||
},
|
||||
open(recordUrl, albumArtUrl, audioUrl, waveformUrl) {
|
||||
this.resetForm();
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
this.albumArtUrl = albumArtUrl;
|
||||
this.waveformUrl = waveformUrl;
|
||||
this.recordUrl = recordUrl;
|
||||
this.audioUrl = audioUrl;
|
||||
|
||||
this.$refs.modal.show();
|
||||
|
||||
this.axios.get(recordUrl).then((resp) => {
|
||||
let d = resp.data;
|
||||
|
||||
this.songLength = d.length_text;
|
||||
this.form = {
|
||||
path: d.path,
|
||||
title: d.title,
|
||||
artist: d.artist,
|
||||
album: d.album,
|
||||
genre: d.genre,
|
||||
lyrics: d.lyrics,
|
||||
isrc: d.isrc,
|
||||
amplify: d.amplify,
|
||||
fade_overlap: d.fade_overlap,
|
||||
fade_in: d.fade_in,
|
||||
fade_out: d.fade_out,
|
||||
cue_in: d.cue_in,
|
||||
cue_out: d.cue_out,
|
||||
playlists: _.map(d.playlists, 'id'),
|
||||
custom_fields: {}
|
||||
};
|
||||
|
||||
_.forEach(this.customFields.slice(), (field) => {
|
||||
this.form.custom_fields[field.short_name] = _.defaultTo(d.custom_fields[field.short_name], null);
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
}).catch(() => {
|
||||
this.close();
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
clearContents() {
|
||||
this.resetForm();
|
||||
this.v$.$reset();
|
||||
},
|
||||
doEdit() {
|
||||
this.v$.$touch();
|
||||
if (this.v$.$errors.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.error = null;
|
||||
|
||||
this.axios.put(this.recordUrl, this.form).then(() => {
|
||||
this.$notifySuccess();
|
||||
this.$emit('relist');
|
||||
this.close();
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
form.value = newForm;
|
||||
}).catch(() => {
|
||||
close();
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const {notifySuccess} = useNotify();
|
||||
|
||||
const doEdit = () => {
|
||||
v$.value.$touch();
|
||||
if (v$.value.$errors.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
error.value = null;
|
||||
|
||||
axios.put(recordUrl.value, form.value).then(() => {
|
||||
notifySuccess();
|
||||
emit('relist');
|
||||
close();
|
||||
}).catch((error) => {
|
||||
error.value = error.response.data.message;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -6,133 +6,131 @@
|
|||
</p>
|
||||
|
||||
<b-form-group>
|
||||
<waveform ref="waveform" :audio-url="audioUrl" :waveform-url="waveformUrl"
|
||||
@ready="updateRegions"></waveform>
|
||||
<waveform-component ref="waveform" :audio-url="audioUrl" :waveform-url="waveformUrl"
|
||||
@ready="updateRegions"></waveform-component>
|
||||
</b-form-group>
|
||||
<b-form-group>
|
||||
<b-button-group>
|
||||
<b-button variant="light" @click="playAudio">
|
||||
<icon icon="play_arrow"></icon>
|
||||
<span class="sr-only">{{ $gettext('Play') }}</span>
|
||||
</b-button>
|
||||
<b-button variant="dark" @click="stopAudio">
|
||||
<icon icon="stop"></icon>
|
||||
<span class="sr-only">{{ $gettext('Stop') }}</span>
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group>
|
||||
<b-button variant="primary" @click="setCueIn">
|
||||
{{ $gettext('Set Cue In') }}
|
||||
</b-button>
|
||||
<div class="buttons">
|
||||
<b-button-group size="sm">
|
||||
<b-button variant="light" @click="playAudio" :title="$gettext('Play')" size="sm">
|
||||
<icon icon="play_arrow"></icon>
|
||||
</b-button>
|
||||
<b-button variant="dark" @click="stopAudio" :title="$gettext('Stop')" size="sm">
|
||||
<icon icon="stop"></icon>
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group size="sm">
|
||||
<b-button variant="primary" @click="setCueIn" size="sm">
|
||||
{{ $gettext('Set Cue In') }}
|
||||
</b-button>
|
||||
<b-button variant="primary" @click="setCueOut" size="sm">
|
||||
{{ $gettext('Set Cue Out') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group size="sm">
|
||||
<b-button variant="warning" @click="setFadeOverlap" size="sm">
|
||||
{{ $gettext('Set Overlap') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group size="sm">
|
||||
<b-button variant="danger" @click="setFadeIn" size="sm">
|
||||
{{ $gettext('Set Fade In') }}
|
||||
</b-button>
|
||||
|
||||
<b-button variant="primary" @click="setCueOut">
|
||||
{{ $gettext('Set Cue Out') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group>
|
||||
<b-button variant="warning" @click="setFadeOverlap">
|
||||
{{ $gettext('Set Overlap') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button-group>
|
||||
<b-button variant="danger" @click="setFadeIn">
|
||||
{{ $gettext('Set Fade In') }}
|
||||
</b-button>
|
||||
|
||||
<b-button variant="danger" @click="setFadeOut">
|
||||
{{ $gettext('Set Fade Out') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
<b-button variant="danger" @click="setFadeOut" size="sm">
|
||||
{{ $gettext('Set Fade Out') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Waveform from '~/components/Common/Waveform';
|
||||
<script setup>
|
||||
import WaveformComponent from '~/components/Common/Waveform';
|
||||
import Icon from '~/components/Common/Icon';
|
||||
import {ref} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'MediaFormWaveformEditor',
|
||||
components: {Icon, Waveform},
|
||||
props: {
|
||||
form: Object,
|
||||
audioUrl: String,
|
||||
waveformUrl: String
|
||||
},
|
||||
methods: {
|
||||
playAudio() {
|
||||
this.$refs.waveform.play();
|
||||
},
|
||||
stopAudio() {
|
||||
this.$refs.waveform.stop();
|
||||
},
|
||||
setCueIn() {
|
||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
||||
const props = defineProps({
|
||||
form: Object,
|
||||
audioUrl: String,
|
||||
waveformUrl: String
|
||||
});
|
||||
|
||||
this.form.cue_in = Math.round((currentTime) * 10) / 10;
|
||||
const waveform = ref(); // Waveform
|
||||
|
||||
this.updateRegions();
|
||||
},
|
||||
setCueOut() {
|
||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
||||
const playAudio = () => {
|
||||
waveform.value?.play();
|
||||
};
|
||||
|
||||
this.form.cue_out = Math.round((currentTime) * 10) / 10;
|
||||
const stopAudio = () => {
|
||||
waveform.value?.stop();
|
||||
};
|
||||
|
||||
this.updateRegions();
|
||||
},
|
||||
setFadeOverlap() {
|
||||
let duration = this.$refs.waveform.getDuration();
|
||||
let cue_out = this.form.cue_out || duration;
|
||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
||||
const updateRegions = () => {
|
||||
let duration = waveform.value?.getDuration();
|
||||
|
||||
this.form.fade_overlap = Math.round((cue_out - currentTime) * 10) / 10;
|
||||
let cue_in = props.form.cue_in ?? 0;
|
||||
let cue_out = props.form.cue_out ?? duration;
|
||||
let fade_overlap = props.form.fade_overlap ?? 0;
|
||||
let fade_in = props.form.fade_in ?? 0;
|
||||
let fade_out = props.form.fade_out ?? 0;
|
||||
|
||||
this.updateRegions();
|
||||
},
|
||||
setFadeIn() {
|
||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
||||
let cue_in = this.form.cue_in || 0;
|
||||
waveform.value?.clearRegions();
|
||||
|
||||
this.form.fade_in = Math.round((currentTime - cue_in) * 10) / 10;
|
||||
// Create cue region
|
||||
waveform.value?.addRegion(cue_in, cue_out, 'hsla(207,90%,54%,0.4)');
|
||||
|
||||
this.updateRegions();
|
||||
},
|
||||
setFadeOut() {
|
||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
||||
let duration = this.$refs.waveform.getDuration();
|
||||
let cue_out = this.form.cue_out || duration;
|
||||
// Create overlap region
|
||||
if (fade_overlap > cue_in) {
|
||||
waveform.value?.addRegion(cue_out - fade_overlap, cue_out, 'hsla(29,100%,48%,0.4)');
|
||||
}
|
||||
|
||||
this.form.fade_out = Math.round((cue_out - currentTime) * 10) / 10;
|
||||
|
||||
this.updateRegions();
|
||||
},
|
||||
updateRegions() {
|
||||
let duration = this.$refs.waveform.getDuration();
|
||||
|
||||
let cue_in = this.form.cue_in || 0;
|
||||
let cue_out = this.form.cue_out || duration;
|
||||
|
||||
let fade_overlap = this.form.fade_overlap;
|
||||
let fade_in = this.form.fade_in;
|
||||
let fade_out = this.form.fade_out;
|
||||
|
||||
this.$refs.waveform.clearRegions();
|
||||
|
||||
// Create cue region
|
||||
this.$refs.waveform.addRegion(cue_in, cue_out, 'hsla(207,90%,54%,0.4)');
|
||||
|
||||
// Create overlap region
|
||||
if (fade_overlap > cue_in) {
|
||||
this.$refs.waveform.addRegion(cue_out - fade_overlap, cue_out, 'hsla(29,100%,48%,0.4)');
|
||||
}
|
||||
|
||||
// Create fade regions
|
||||
if (fade_in) {
|
||||
this.$refs.waveform.addRegion(cue_in, fade_in + cue_in, 'hsla(351,100%,48%,0.4)');
|
||||
}
|
||||
if (fade_out) {
|
||||
this.$refs.waveform.addRegion(cue_out - fade_out, cue_out, 'hsla(351,100%,48%,0.4)');
|
||||
}
|
||||
}
|
||||
// Create fade regions
|
||||
if (fade_in) {
|
||||
waveform.value?.addRegion(cue_in, fade_in + cue_in, 'hsla(351,100%,48%,0.4)');
|
||||
}
|
||||
if (fade_out) {
|
||||
waveform.value?.addRegion(cue_out - fade_out, cue_out, 'hsla(351,100%,48%,0.4)');
|
||||
}
|
||||
};
|
||||
|
||||
const setCueIn = () => {
|
||||
let currentTime = waveform.value?.getCurrentTime();
|
||||
|
||||
props.form.cue_in = Math.round((currentTime) * 10) / 10;
|
||||
updateRegions();
|
||||
};
|
||||
|
||||
const setCueOut = () => {
|
||||
let currentTime = waveform.value?.getCurrentTime();
|
||||
|
||||
props.form.cue_out = Math.round((currentTime) * 10) / 10;
|
||||
updateRegions();
|
||||
};
|
||||
|
||||
const setFadeOverlap = () => {
|
||||
let duration = waveform.value?.getDuration();
|
||||
let currentTime = waveform.value?.getCurrentTime();
|
||||
let cue_out = form.value?.cue_out ?? duration;
|
||||
|
||||
props.form.fade_overlap = Math.round((cue_out - currentTime) * 10) / 10;
|
||||
updateRegions();
|
||||
};
|
||||
|
||||
const setFadeIn = () => {
|
||||
let currentTime = waveform.value?.getCurrentTime();
|
||||
let cue_in = form.value?.cue_in ?? 0;
|
||||
|
||||
props.form.fade_in = Math.round((currentTime - cue_in) * 10) / 10;
|
||||
updateRegions();
|
||||
}
|
||||
|
||||
const setFadeOut = () => {
|
||||
let currentTime = waveform.value?.getCurrentTime();
|
||||
let duration = waveform.value?.getDuration();
|
||||
let cue_out = form.value?.cue_out ?? duration;
|
||||
|
||||
props.form.fade_out = Math.round((cue_out - currentTime) * 10) / 10;
|
||||
updateRegions();
|
||||
};
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue