You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.1 KiB
204 lines
5.1 KiB
<template>
|
|
<div v-if="!microphoneError">
|
|
<div class="flex flex-row">
|
|
<div v-if="recorderState.isRecording" class="flex rounded-md mr-2 mb-2 py-2 px-3 border border-error">
|
|
<BaseIcon class="self-center mr-2 text-error motion-safe:animate-pulse" icon="microphone"/>
|
|
<p class="self-center font-semibold text-error">
|
|
{{ recordedTime }}
|
|
</p>
|
|
</div>
|
|
|
|
<BaseButton
|
|
v-if="showButtons && recorderState.isRecording"
|
|
:label="t('Stop recording')"
|
|
class="mr-2 mb-2"
|
|
icon="stop"
|
|
type="danger"
|
|
@click="stop"
|
|
/>
|
|
<BaseButton
|
|
v-else-if="showButtons"
|
|
:label="t('Start recording')"
|
|
class="mr-2 mb-2"
|
|
icon="microphone"
|
|
type="primary"
|
|
@click="record"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="showRecordedAudios">
|
|
<div v-for="(audio, index) in recorderState.audioList" :key="index" class="py-2">
|
|
<audio class="max-w-full" controls>
|
|
<source :src="window.URL.createObjectURL(audio)"/>
|
|
</audio>
|
|
|
|
<BaseButton
|
|
:label="$t('Attach')"
|
|
class="my-1"
|
|
icon="attachment"
|
|
size="small"
|
|
type="success"
|
|
@click="attachAudio(audio)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<p>
|
|
{{ microphoneError }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import BaseButton from "./basecomponents/BaseButton.vue";
|
|
import { computed, reactive, ref, onMounted } from "vue";
|
|
import { RecordRTCPromisesHandler, StereoAudioRecorder } from "recordrtc";
|
|
import { useI18n } from "vue-i18n";
|
|
import BaseIcon from "./basecomponents/BaseIcon.vue";
|
|
|
|
const { t } = useI18n();
|
|
|
|
const props = defineProps({
|
|
multiple: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
showButtons: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
showRecordedAudios: {
|
|
type: Boolean,
|
|
default: true,
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(["attach-audio", "recorded-audio"]);
|
|
|
|
defineExpose({
|
|
record,
|
|
stop,
|
|
});
|
|
|
|
const recorderState = reactive({
|
|
isRecording: false,
|
|
audioList: [],
|
|
seconds: 0,
|
|
minutes: 0,
|
|
hours: 0,
|
|
});
|
|
|
|
const microphoneError = ref('');
|
|
|
|
const recordedTime = computed(() => {
|
|
let hours = timeComponentToString(recorderState.hours)
|
|
let minutes = timeComponentToString(recorderState.minutes)
|
|
let seconds = timeComponentToString(recorderState.seconds)
|
|
return `${hours} : ${minutes} : ${seconds}`
|
|
})
|
|
|
|
onMounted(() => {
|
|
let isMediaDevicesSupported = navigator.mediaDevices
|
|
&& navigator.mediaDevices.getUserMedia
|
|
|
|
if (!isMediaDevicesSupported) {
|
|
console.warn('Either your browser does not support microphone or your are serving your site from not secure ' +
|
|
'context, check https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia for more information');
|
|
microphoneError.value = t('We\'re sorry, your browser does not support using a microphone');
|
|
}
|
|
});
|
|
|
|
let recorder = null;
|
|
|
|
async function record() {
|
|
if (microphoneError.value) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
|
|
recorder = new RecordRTCPromisesHandler(stream, {
|
|
recorderType: StereoAudioRecorder,
|
|
type: "audio",
|
|
mimeType: "audio/wav",
|
|
numberOfAudioChannels: 2,
|
|
});
|
|
recorder.startRecording();
|
|
startTimer()
|
|
|
|
recorderState.isRecording = true;
|
|
} catch (error) {
|
|
console.warn('Either the user denied permission or microphone is not available, ' +
|
|
'check https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia');
|
|
if (error.name === 'NotAllowedError') {
|
|
microphoneError.value = t('Permission to use the microphone is not enabled, please enable it in your browser to record audio');
|
|
} else {
|
|
microphoneError.value = t('We\'re sorry, your browser does not support using a microphone');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function stop() {
|
|
if (!recorder) {
|
|
return;
|
|
}
|
|
|
|
if (false === props.multiple && recorderState.audioList.length > 0) {
|
|
recorderState.audioList.shift();
|
|
}
|
|
|
|
await recorder.stopRecording();
|
|
|
|
const audioBlob = await recorder.getBlob();
|
|
|
|
recorderState.audioList.push(audioBlob);
|
|
emit('recorded-audio', audioBlob);
|
|
|
|
recorderState.isRecording = false;
|
|
stopTimer();
|
|
}
|
|
|
|
function attachAudio(audio) {
|
|
emit("attach-audio", audio);
|
|
|
|
const index = recorderState.audioList.indexOf(audio);
|
|
|
|
if (index >= 0) {
|
|
recorderState.audioList.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
let timer = null;
|
|
function startTimer() {
|
|
recorderState.seconds = 0;
|
|
recorderState.minutes = 0;
|
|
recorderState.hours = 0;
|
|
|
|
timer = setInterval(() => {
|
|
recorderState.seconds = recorderState.seconds + 1
|
|
if (recorderState.seconds > 59) {
|
|
recorderState.minutes = recorderState.minutes + 1
|
|
recorderState.seconds = 0
|
|
}
|
|
if (recorderState.minutes > 59) {
|
|
recorderState.hours = recorderState.hours + 1
|
|
recorderState.minutes = 0
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function stopTimer() {
|
|
recorderState.seconds = 0;
|
|
recorderState.minutes = 0;
|
|
recorderState.hours = 0;
|
|
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
|
|
function timeComponentToString(value) {
|
|
return value.toString().padStart(2, '0');
|
|
}
|
|
</script>
|
|
|