|
@@ -0,0 +1,192 @@
|
|
|
+<template>
|
|
|
+ <div class="speech">
|
|
|
+ <svg
|
|
|
+ v-if="!isSpeech"
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="24"
|
|
|
+ height="24"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ >
|
|
|
+ <rect width="2.8" height="12" x="1" y="6" fill="#8ac5ff" />
|
|
|
+ <rect width="2.8" height="12" x="5.8" y="6" fill="#8ac5ff" />
|
|
|
+ <rect width="2.8" height="12" x="10.6" y="6" fill="#8ac5ff" />
|
|
|
+ <rect width="2.8" height="12" x="15.4" y="6" fill="#8ac5ff" />
|
|
|
+ <rect width="2.8" height="12" x="20.2" y="6" fill="#8ac5ff" />
|
|
|
+ </svg>
|
|
|
+ <Icon
|
|
|
+ v-else
|
|
|
+ class="icon"
|
|
|
+ icon="svg-spinners:bars-scale-middle"
|
|
|
+ width="24"
|
|
|
+ height="24"
|
|
|
+ animation="0.7"
|
|
|
+ color="#8ac5ff"
|
|
|
+ />
|
|
|
+ <!-- <div class="second">{{ second }}s</div> -->
|
|
|
+ <a-tooltip :content="$t('previewList.hh')">
|
|
|
+ <a-button
|
|
|
+ @mousedown="speechDown"
|
|
|
+ @mouseup="speechUp"
|
|
|
+ @mouseleave="speechUp"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <Icon
|
|
|
+ class="icon"
|
|
|
+ icon="tabler:microphone"
|
|
|
+ width="24"
|
|
|
+ height="24"
|
|
|
+ color="#8ac5ff"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </a-button>
|
|
|
+ </a-tooltip>
|
|
|
+ <a-tooltip :content="$t('previewList.bt')">
|
|
|
+ <a-button
|
|
|
+ v-if="!isPlay"
|
|
|
+ :disabled="audioChunks.length === 0"
|
|
|
+ @click="speechPlay"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <Icon
|
|
|
+ class="icon"
|
|
|
+ icon="majesticons:play-circle-line"
|
|
|
+ width="24"
|
|
|
+ height="24"
|
|
|
+ color="#8ac5ff"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </a-button>
|
|
|
+ <a-button v-else @click="speechStop">
|
|
|
+ <template #icon>
|
|
|
+ <Icon
|
|
|
+ class="icon"
|
|
|
+ icon="gg:play-stop-o"
|
|
|
+ width="24"
|
|
|
+ height="24"
|
|
|
+ color="#8ac5ff"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </a-button>
|
|
|
+ </a-tooltip>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref } from 'vue';
|
|
|
+ import { Icon } from '@iconify/vue';
|
|
|
+ import { Message } from '@arco-design/web-vue';
|
|
|
+ import { useAppStore } from '@/store';
|
|
|
+
|
|
|
+ const app = useAppStore();
|
|
|
+ const isSpeech = ref(false);
|
|
|
+ const isPlay = ref(false);
|
|
|
+ const audioChunks = ref<any[]>([]);
|
|
|
+ let mediaRecorder: MediaRecorder;
|
|
|
+ let audioUrl;
|
|
|
+ let audioBlob: any;
|
|
|
+ let audio: any;
|
|
|
+ const second = ref(0);
|
|
|
+
|
|
|
+ // 获取用户的麦克风权限并开始录音
|
|
|
+ const getMicrophone = async () => {
|
|
|
+ let previousTime = Date.now();
|
|
|
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
|
+
|
|
|
+ mediaRecorder = new MediaRecorder(stream);
|
|
|
+ mediaRecorder.ondataavailable = (event) => {
|
|
|
+ second.value += 1;
|
|
|
+ const timeNow = Date.now();
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log(`Interval: ${(timeNow - previousTime) / 1000}s`);
|
|
|
+ previousTime = timeNow;
|
|
|
+ audioChunks.value.push(event.data);
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onstop = () => {
|
|
|
+ audio = document.createElement('audio');
|
|
|
+ audio.controls = true;
|
|
|
+ audioBlob = new Blob(audioChunks.value, { type: 'audio/wav' });
|
|
|
+ audioUrl = URL.createObjectURL(audioBlob);
|
|
|
+ audio.src = audioUrl;
|
|
|
+ };
|
|
|
+
|
|
|
+ if (audio) {
|
|
|
+ audio = null;
|
|
|
+ audioChunks.value = [];
|
|
|
+ }
|
|
|
+ mediaRecorder.start();
|
|
|
+ Message.info('开始录音');
|
|
|
+ };
|
|
|
+
|
|
|
+ // 停止录音
|
|
|
+ const stopMicrophone = async () => {
|
|
|
+ if (!isSpeech.value) return;
|
|
|
+ mediaRecorder.stop();
|
|
|
+ Message.info('录音结束');
|
|
|
+ };
|
|
|
+
|
|
|
+ // 播放录音
|
|
|
+ const playMicrophone = async () => {
|
|
|
+ audio.play();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 将录音发送到服务器
|
|
|
+ const sendMicrophone = async () => {
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('audio', audioBlob, 'recording.wav');
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${app.ip}/IM/AudioFile`, {
|
|
|
+ method: 'PUT',
|
|
|
+ body: formData,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ Message.success('音频上传成功');
|
|
|
+ // alert('Audio uploaded successfully');
|
|
|
+ } else {
|
|
|
+ // alert('Failed to upload audio');
|
|
|
+ Message.error('上传音频失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ Message.error('上传音频失败');
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.error('Error uploading audio:', error);
|
|
|
+ // alert('Error uploading audio');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const speechDown = () => {
|
|
|
+ isSpeech.value = true;
|
|
|
+ getMicrophone();
|
|
|
+ };
|
|
|
+ const speechUp = () => {
|
|
|
+ stopMicrophone();
|
|
|
+ isSpeech.value = false;
|
|
|
+ sendMicrophone();
|
|
|
+ };
|
|
|
+
|
|
|
+ const speechStop = () => {
|
|
|
+ isPlay.value = false;
|
|
|
+ };
|
|
|
+ const speechPlay = () => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log('播放开始');
|
|
|
+ isPlay.value = true;
|
|
|
+ playMicrophone();
|
|
|
+ audio.onended = () => {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log('播放结束');
|
|
|
+ speechStop();
|
|
|
+ };
|
|
|
+ };
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+ .speech {
|
|
|
+ height: 40px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ gap: 15px;
|
|
|
+ }
|
|
|
+</style>
|