喊话器功能

阅读 21

2024-11-25

喊话器功能_上传

 

喊话器功能_json_02

 

喊话器功能_上传_03

 

<script setup lang="ts">
import { Modal, message } from 'ant-design-vue';
import Speech from 'speak-tts';
import { useStorage } from '@vueuse/core';
import { createErrorMsg } from '@/api/helper.ts';

const dialogRef2 = ref();
const voiceTypeTabs = ref([
  {
    label: '实时喊话',
    value: '0',
  },
  {
    label: '音频',
    value: '1',
  },
  {
    label: '语言合成',
    value: '2',
  },
]);
const CURRENT_EQIUP: any = useStorage('CURRENT_EQUIP', {}, sessionStorage);
const dockSn = computed(() => CURRENT_EQIUP.value.dock || '');
const activedVioce = ref('0');
const voiceSlider = ref<number>(100);// 音量
const voviceOpen = ref(false); // 音量弹窗是否开启
const speech = new Speech();
const voiceText = ref('');

function open() {
  // 清空和停止之前的内容播放
  voiceText.value = '';
  speech.cancel();
  dialogRef2.value?.open();
}
function close() {
  console.log('关闭');
}

// 获取音频项目
const audio = ref<HTMLAudioElement>();
const audioUrl = ref('');
const audioIndex = ref();
// const fileList = ref([]);
const queryParamsyp = ref({
  currentPage: 1,
  pageSize: 5,
});
const total: any = ref(0);
const queryParamsyy = ref({
  currentPage: 1,
  pageSize: 5,
});
// 音频列表
const voiceList: any = ref([
  // {
  //   fileIndex: 0,
  //   key: '2024/10/16/080bc94167ee4c1f935ab02e629a48c1.mp3',
  //   fileName: '音频1.mp3',
  //   msg: '成功',
  //   url: 'https://www.teleuav.com:20000/api/file-service/file/view?key=1815209819035451393/2024/11/22/5d6246a09c154557b37e51f986180b31.mp3',
  //   compressKey: null,
  //   id: '1846381305490059265',
  //   isPlay: false,
  //   isEdit: true,
  // },
  // {
  //   fileIndex: 0,
  //   key: '2024/10/16/99821753ff2c4d18b255f18bfe9d45f1.mp3',
  //   fileName: '音频2.mp3',
  //   msg: '成功',
  //   url: 'https://www.teleuav.com:20000/api/file-service/file/view?key=1815209819035451393/2024/11/22/5d6246a09c154557b37e51f986180b31.mp3',
  //   compressKey: null,
  //   id: '1846448057976631297',
  //   isPlay: false,
  //   isEdit: true,
  // },
]);
function _postApiSpeakerPageQuery(vtype: any, currentPage: any, pageSize: any) {
  postApiSpeakerPageQuery({
    currentPage,
    pageSize,
    dockSn: dockSn.value,
    contentType: vtype, // 0文本,1音频
  }).then((res) => {
    console.log('列表111', res);
    if (res.success) {
      if (res.data && res.data !== null) {
        const d = res.data.records;
        d?.forEach((item) => {
          item.isEdit = true;
        });
        voiceList.value = d;
        total.value = res.data.total;
      }
    }
  });
}
function voiceTypeTabschange(e: any) {
  if (e.value === '1') {
    // 音频
    _postApiSpeakerPageQuery(1, queryParamsyp.value.currentPage, queryParamsyp.value.pageSize);
  }
  else if (e.value === '2') {
    // 语言
    _postApiSpeakerPageQuery(0, queryParamsyy.value.currentPage, queryParamsyy.value.pageSize);
  }
}

function onChangePageyp(current: number, pageSize: number) {
  queryParamsyp.value.currentPage = current;
  queryParamsyp.value.pageSize = pageSize;
}
function onChangePageyy(current: number, pageSize: number) {
  queryParamsyy.value.currentPage = current;
  queryParamsyy.value.pageSize = pageSize;
}
// function isAudioFile(file: any) {
//   const audioMimeTypes = [
//     'audio/mpeg',
//     'audio/mp3',
//     'audio/mp4',
//     'audio/pmc',
//     'audio/webm',
//     'audio/ogg',
//     'audio/wav',
//     'audio/x-ms-wma',
//     'audio/x-ms-wma',
//     'audio/x-realaudio',
//     'audio/vnd.rn-realaudio',
//     'audio/x-pn-realaudio',
//     'audio/x-wav',
//   ];
//   return audioMimeTypes.includes(file.type);
// }
// 上传前对文件进行校验
// function beforeUpload(file: any) {
//   if (!isAudioFile(file)) {
//     message.warning('请上传音频格式的文件!');
//     return false;
//   }
// const fileExtension = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();
// if (fileExtension !== 'zip') {
//   message.warning('请上传zip格式的文件!');
//   return;
// }
// handleAudioUpload(file);
//   return false;
// }

// 音频保存
// function handleAudioUpload(file: any) {
//   console.log('file上传', file);
//   const res = {
//     code: 0,
//     msg: '操作成功',
//     hint: '',
//     data: {
//       fileIndex: 0,
//       key: '2024/10/16/080bc94167ee4c1f935ab02e629a48c1.mp3',
//       fileName: '080bc94167ee4c1f935ab02e629a48c1.mp3',
//       msg: '成功',
//       url: 'http://125.122.14.45:20000/api/file-service/file/view?key=2024/10/16/080bc94167ee4c1f935ab02e629a48c1.mp3',
//       compressKey: null,
//       id: '1846381305490059265',
//     },
//     success: true,
//   };
//   if (res.success) {
//     const json = res.data;
//     voiceList.value.push(json);
//     console.log('voiceList.value', voiceList.value);
//   }
// postFileUpload({}, {}, file).then((res) => {
//     console.log('res上传',res)
//     {
//     "code": 0,
//     "msg": "操作成功",
//     "hint": "",
//     "data": {
//         "fileIndex": 0,
//         "key": "2024/10/16/080bc94167ee4c1f935ab02e629a48c1.mp3",
//         "fileName": "080bc94167ee4c1f935ab02e629a48c1.mp3",
//         "msg": "成功",
//         "url": "http://125.122.14.45:20000/api/file-service/file/view?key=2024/10/16/080bc94167ee4c1f935ab02e629a48c1.mp3",
//         "compressKey": null,
//         "id": "1846381305490059265"
//     },
//     "success": true
// }
// if (res?.success) {
//   message.success('上传成功');
//   const json = res.data;
//   voiceList.value.push(json);
// }
// });
// }
// 播放当前项
function playItem(index: any, item: any) {
  console.log(item);
  audioIndex.value = index;
  audioUrl.value = item.contentUrl; // 获取当前播放音频
  // 当前播放 其他停止
  voiceList.value.forEach((item2) => {
    if (item2.id === item.id) {
      item2.isPlay = !item2.isPlay;
      if (item2.isPlay) {
        nextTick(() => {
          audio?.value?.play();
        });
      }
    }
    else {
      item2.isPlay = false;
      audio?.value?.pause();
    }
  });
}
// 音频是否播放完毕
function audioEnded() {
  voiceList.value[audioIndex.value].isPlay = false;
}
// 音频发送
function fasongItem(index: any, item: any) {
  Modal.confirm({
    title: '提示',
    content: '确认要喊话吗?',
    okText: '确认',
    cancelText: '取消',
    async onOk() {
      postApiSpeakerShoutContent({
        dockSn: item.dockSn,
        id: item.id,
      }).then((res) => {
        if (res.success) {
          message.success(res.msg);
        }
        else {
          message.error(res.msg);
        }
      });
    },
    onCancel() { },
  });
}
// 编辑音频名称
function editItem(item: any) {
  item.isEdit = !item.isEdit;
  if (item.isEdit) {
    postApiSpeakerUpdateContent({
      dockSn: item.dockSn,
      fileName: item.fileName,
      id: item.id,
    }).then((res) => {
      if (res.success) {
        message.success(res.msg);
        _postApiSpeakerPageQuery(1, queryParamsyp.value.currentPage, queryParamsyp.value.pageSize);
      }
      else {
        message.error(res.msg);
      }
    });
  }
}
// 删除当前音频
function deleteItem(index: any, item: any) {
  console.log('item', item);
  Modal.confirm({
    title: '提示',
    content: '确认删除当前内容?',
    okText: '确认',
    cancelText: '取消',
    async onOk() {
      // 删除和停止播放
      // voiceList.value.splice(index, 1);
      postApiSpeakerDelContent({
        dockSn: item.dockSn,
        id: item.id,
      }).then((res) => {
        if (res.success) {
          if (index === audioIndex.value) {
            audio?.value?.pause();
          }
          message.success(res.msg);
          if (item.contentType === 0) {
            _postApiSpeakerPageQuery(0, queryParamsyy.value.currentPage, queryParamsyy.value.pageSize);
          }
          else {
            _postApiSpeakerPageQuery(1, queryParamsyp.value.currentPage, queryParamsyp.value.pageSize);
          }
        }
        else {
          message.error(res.msg);
        }
      });
    },
    onCancel() { },
  });
}
// function removefile(file: any) {
//   console.log('file', file);
//   console.log(fileList, 123);
// }

/*
语言转换
*/
const voiceisPlay = ref<boolean>(false);
if (speech.hasBrowserSupport()) { // 检测浏览器是否支持,returns a boolean
  console.log('语音引擎加载成功');
}
else {
  console.log('此浏览器不支持语音播报');
}
speech.init({
  volume: 1, // 音量0-1
  lang: 'zh-CN', // 语言
  rate: 1, // 语速1正常语速,2倍语速就写2
  pitch: 1, // 音调
  voice: 'Microsoft Yaoyao - Chinese (Simplified, PRC)', // 支持Microsoft Huihui - Chinese (Simplified, PRC),Microsoft Kangkang - Chinese (Simplified, PRC),Microsoft Yaoyao - Chinese (Simplified, PRC)
  listeners: {
    // 事件
    onvoiceschanged: (voices: any) => {
      console.log('事件声音已更改', voices);
    },
  },
})
  .then((data: any) => {
    console.log('语音已准备好,声音可用', data);
  })
  .catch((e: any) => {
    console.error('初始化时发生错误 : ', e);
  });
// 音频转换播放
function playvoice() {
  speech.resume();
  speech.speak({
    text: voiceText.value, // 这里使用文字或者i18n 都可以 看自己需求
    // queue: true,
    listeners: {
      //  开始播放
      onstart: () => {
        console.log('开始播放');
        voiceisPlay.value = true;
      },
      //  判断播放是否完毕
      onend: () => {
        console.log('播放是否完毕');
        voiceisPlay.value = false;
      },
      //  恢复播放
      onresume: () => {
        console.log('恢复播放');
        voiceisPlay.value = true;
        speech.resume();
      },
    },
  })
    .then(() => {
      console.log('播放成功!');
    })
    .catch((e: any) => {
      console.error('发生错误:', e);
    });
}
function stopvoice() {
  voiceisPlay.value = false;
  speech.pause();
}
function deletevoice() {
  voiceText.value = '';
  speech.cancel();
  voiceisPlay.value = false;
}

// 内容变化
function textareachange() {
  speech.cancel();
  voiceisPlay.value = false;
}

// 语言音频保存
function savevoice() {
  if (!voiceText.value) {
    createErrorMsg('请输入文本内容');
    return;
  }
  const data = {
    dockSn: dockSn.value,
    contentType: 0, // 0-文本,1音频
    contentUrl: voiceText.value, // 文本文字或喊话url
    // contentAuditionUrl: '' //音频喊话pcm格式的url
  };
  postApiSpeakerAddContent(data).then((res) => {
    if (res.success) {
      message.success(res.msg);
      voiceText.value = '';
      _postApiSpeakerPageQuery(0, queryParamsyy.value.currentPage, queryParamsyy.value.pageSize);
    }
    else {
      message.error(res.msg);
    }
  });
}
// 设置音量
function openVoviceModal() {
  voviceOpen.value = true;
  // 查询喊话音量大小
  getApiSpeakerGetVolume({
    dockSn: dockSn.value,
  }).then((res) => {
    if (res.success) {
      if (res.data && res.data !== null) {
        voiceSlider.value = res.data;
      }
    }
  });
}
// 更改音频音量
const changeVoice = function (e: any) {
  if (e) {
    voiceSlider.value = e;
  }
};
// 音量保存
function handleOk() {
  const params = {
    dockSn: dockSn.value,
    volume: voiceSlider.value,
  };
  postApiSpeakerUpdateVolume(params).then((res) => {
    if (res.success) {
      message.success(res.msg);
      voviceOpen.value = false;
    }
    else {
      message.error(res.msg);
    }
  });
}
// 录音保存成功刷新列表
function recorderupdata() {
  _postApiSpeakerPageQuery(1, queryParamsyp.value.currentPage, queryParamsyp.value.pageSize);
}
defineExpose({
  open,
});
</script>

<template>
  <Dialog
    ref="dialogRef2" :footer="false" :dialog-style="{ width: '25rem', top: '25%', right: '7rem', zIndex: 1049 }"
    title="喊话器" @close="close"
  >
    <div class="voice_container">
      <!-- 设置音量 -->
      <div class="mt-[-28px]" @click="openVoviceModal">
        <a-tooltip title="音量设置" placement="top">
          <PubSvgIcon name="icon_set" class="cursor-pointer" :size="22" />
        </a-tooltip>
      </div>
      <div class="relative h-12 px-4 flex items-center justify-center">
        <TabBox v-model="activedVioce" :tabs="voiceTypeTabs" @change="voiceTypeTabschange" />
      </div>
      <div
        v-if="activedVioce === '0'" class="w-full h-full flex items-center justify-center my-9"
        style="flex-direction: column;"
      >
        <!-- 实时喊话录音 -->
        <recorder />
      </div>
      <!-- 音频 -->
      <div v-if="activedVioce === '1'">
        <div v-if="voiceList && voiceList.length">
          <div
            v-for="(item, index) in voiceList" :key="index"
            class="flex px-2 mb-3 h-8 line-clamp-1 bg-[#11253E] items-center mt-1"
          >
            <div class="grid-cols-3 whitespace-nowrap list_item_left">
              <!-- <span>{{ item.fileName }}</span> -->
              <a-input v-model:value="item.fileName" :disabled="item.isEdit" :maxlength="10">
                <template #suffix>
                  <a-tooltip title="编辑" placement="top">
                    <PubSvgIcon name="icon_edit" class="cursor-pointer" :size="22" @click="editItem(item)" />
                  </a-tooltip>
                </template>
              </a-input>
            </div>
            <span class="flex items-center h-full">
              <a-tooltip title="试听" placement="top">
                <PubSvgIcon
                  v-if="!item.isPlay" class="cursor-pointer" name="icon_play" size="1.5rem" color="#fff"
                  @click="playItem(index, item)"
                />
                <PubSvgIcon
                  v-if="item.isPlay" class="cursor-pointer" name="icon_pause" size="1.5rem" color="#fff"
                  @click="playItem(index, item)"
                />
              </a-tooltip>
              <a-tooltip title="发送" placement="top">
                <PubSvgIcon
                  name="icon_fasong" size="1.5rem" color="#FE3B30" class="ml-1 mr-1 cursor-pointer"
                  @click="fasongItem(index, item)"
                />
              </a-tooltip>
              <a-tooltip title="删除" placement="top">
                <PubSvgIcon
                  name="icon_delete" class="cursor-pointer" size="1.5rem" color="#FE3B30"
                  @click="deleteItem(index, item)"
                />
              </a-tooltip>
            </span>
          </div>
        </div>
        <div v-if="!voiceList || voiceList.length <= 0" class="no-data">
          暂无数据
        </div>

        <a-pagination
          v-model:current="queryParamsyp.currentPage" v-model:page-size="queryParamsyp.pageSize"
          size="small" class="text-center" :total="total" :page-size="5" :show-total="total => `共 ${total} 条`"
          @change="onChangePageyp"
        />
        <div class="flex items-center justify-center">
          <!-- <a-upload
            v-model:file-list="fileList" :before-upload="(file: any) => beforeUpload(file)"
            @remove="removefile"
          >
            <a-button type="primary" class="up-button">
              上传音频
            </a-button>
          </a-upload> -->
          <recorderup @updata="recorderupdata" />
        </div>
      </div>
      <!-- 语言合成 -->
      <div v-if="activedVioce === '2'" class="relative">
        <div v-if="voiceList && voiceList.length">
          <div
            v-for="(item, index) in voiceList" :key="index"
            class="flex px-2 mb-3 h-8 line-clamp-1 bg-[#11253E] items-center mt-1"
          >
            <div class="grid-cols-3 whitespace-nowrap list_item_left">
              <span>{{ item.contentUrl }}</span>
            </div>
            <span class="flex items-center h-full">
              <a-tooltip title="发送" placement="top">
                <PubSvgIcon
                  name="icon_fasong" class="cursor-pointer mr-1" size="1.5rem" color="#FE3B30" cursor-pointer
                  @click="fasongItem(index, item)"
                />
              </a-tooltip>
              <a-tooltip title="删除" placement="top">
                <PubSvgIcon
                  name="icon_delete" size="1.5rem" class="cursor-pointer" color="#FE3B30"
                  @click="deleteItem(index, item)"
                />
              </a-tooltip>
            </span>
          </div>
        </div>
        <div v-if="!voiceList || voiceList.length <= 0" class="no-data">
          暂无数据
        </div>

        <a-pagination
          v-model:current="queryParamsyy.currentPage" v-model:page-size="queryParamsyy.pageSize"
          size="small" class="text-center" :total="total" :page-size="5" :show-total="total => `共 ${total} 条`"
          @change="onChangePageyy"
        />
        <div class="relative mt-2">
          <div class="absolute flex items-center z-30 right-2 top-2">
            <PubSvgIcon
              v-if="!voiceisPlay" name="icon_play" class="cursor-pointer mr-1" size="1.5rem" color="#fff"
              @click="playvoice()"
            />
            <PubSvgIcon
              v-else name="icon_pause" size="1.5rem" color="#fff" class="cursor-pointer mr-1"
              @click="stopvoice()"
            />
            <a-tooltip title="清空" placement="top">
              <PubSvgIcon name="icon_delete" size="1.5rem" color="#FE3B30" @click="deletevoice()" />
            </a-tooltip>
          </div>
          <a-textarea
            v-model:value="voiceText" class="new-textarea" :rows="4" placeholder="请输入符合文本内容" show-count
            :maxlength="300" @change="textareachange"
          />
          <div class="flex items-center justify-end mt-8">
            <a-button type="primary" class="up-button" @click="savevoice">
              保存
            </a-button>
          </div>
        </div>
      </div>
      <!-- 设置音量弹窗 -->
      <a-modal
        v-model:open="voviceOpen" class="new-modal" :mask-closable="false" style="top: 30%;z-index: 1049;"
        @ok="handleOk"
      >
        <div>
          <span class="w-10 font-size-5">音量设置</span>
        </div>
        <div class="mt-1 flex items-center">
          <div class="flex-1">
            <a-slider v-model:value="voiceSlider" class="blue-slider" :min="1" @change="changeVoice" />
          </div>
        </div>
      </a-modal>
      <audio ref="audio" :src="audioUrl" @ended="audioEnded" />
    </div>
  </Dialog>
</template>

<style lang="less" scoped>
:deep(.modal-body) {
  margin: 0;
}

.voice_container {
  margin-top: -10px;
  margin-bottom: -10px;
  filter: none;

  .list_item_left {
    width: 90%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .up-button {
    background-image: linear-gradient(115deg, #3a83fe 10%, #0f63fe 100%);
  }

  :deep(.ant-slider-rail) {
    background-color: #757f89;
  }

  :deep(.ant-slider-track) {
    background: #1f6aff;
  }

  :deep(.ant-slider-handle::after) {
    background: #1f6aff;
    border: 1px solid #fff;
    box-shadow: none;
  }

  :deep(.ant-slider-handle:hover::after) {
    box-shadow: none;
  }

  :deep(.ant-input-textarea-show-count::after) {
    position: absolute;
    right: 8px;
    bottom: 6px;
    color: rgba(255, 255, 255, 0.45);
    white-space: nowrap;
    content: attr(data-count);
  }
}

.new-textarea {
  :deep(.ant-input) {
    padding: 10px 50px 10px 10px;
  }
}

//隐藏上传列表
:deep(.ant-upload-list) {
  display: none !important;
}
</style>

<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { useStorage } from '@vueuse/core';

// 必须引入的核心
import Recorder from 'recorder-core';
import 'recorder-core/src/engine/mp3';
import 'recorder-core/src/engine/mp3-engine';
import 'recorder-core/src/engine/wav';
import 'recorder-core/src/engine/pcm';
import 'recorder-core/src/extensions/waveview';

const emit = defineEmits(['updata']);
const CURRENT_EQIUP: any = useStorage('CURRENT_EQUIP', {}, sessionStorage);
const dockSn = computed(() => CURRENT_EQIUP.value.dock || '');
let rec: any;
let recBlob: any;
let wave: any;
const playing = ref(false);
const recwave = ref(null);
// 打开录音
function recOpen() {
  playing.value = true;
  // 创建录音对象
  rec = Recorder({
    type: 'mp3', // 录音格式,可以换成wav等其他格式
    sampleRate: 16000, // 录音的采样率,越大细节越丰富越细腻
    bitRate: 16, // 录音的比特率,越大音质越好
    format: 'mp3',
    onProcess: (
      buffers: any,
      powerLevel: any,
      // bufferDuration: any,
      bufferSampleRate: any,
      // newBufferIdx: any,
      // asyncEnd: any,
    ) => {
      // 录音实时回调,大约1秒调用12次本回调
      // 可实时绘制波形,实时上传(发送)数据
      if (wave) {
        wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
      }
    },
  });
  if (!rec) {
    // alert('当前浏览器不支持录音功能!');
    return;
  }
  // 打开录音,获得权限
  rec.open(
    () => {
      console.log('录音已打开');
      if (recwave.value) {
        // 创建音频可视化图形绘制对象
        wave = Recorder.WaveView({ elem: recwave.value });
        setTimeout(recStart, 300);
      }
    },
    (msg: any, isUserNotAllow: any) => {
      // 用户拒绝了录音权限,或者浏览器不支持录音
      console.log(`${isUserNotAllow ? 'UserNotAllow,' : ''}无法录音:${msg}`);
    },
  );
}
// 开始录音
function recStart() {
  if (!rec) {
    console.error('未打开录音');
    return;
  }
  rec.start();
  console.log('已开始录音');
}
// 结束录音
const isEnd = ref(false);
function recStop() {
  playing.value = false;
  if (!rec) {
    console.error('未打开录音');
    return;
  }
  rec.stop(
    (blob: any, duration: any) => {
      if (blob) {
        // blob就是我们要的录音文件对象,可以上传,或者本地播放
        recBlob = blob;
        // 简单利用URL生成本地文件地址,此地址只能本地使用,比如赋值给audio.src进行播放,赋值给a.href然后a.click()进行下载(a需提供download="xxx.mp3"属性)
        const localUrl = (window.URL || window.webkitURL).createObjectURL(blob);
        console.log('录音成功', blob, localUrl, `时长:${duration}ms`);
        console.log('recBlob1111', recBlob);
        isEnd.value = true;
      }
      // upload(blob); // 把blob文件上传到服务器
      rec.close(); // 关闭录音,释放录音资源,当然可以不释放,后面可以连续调用start
      rec = null;
    },
    (err: any) => {
      console.error(`结束录音出错:${err}`);
      rec.close(); // 关闭录音,释放录音资源,当然可以不释放,后面可以连续调用start
      rec = null;
    },
  );
}
// 上传录音
const pcmurl: any = ref();
const mp3url: any = ref();

function submit() {
  if (recBlob) {
    const audioFilepcm = new File([recBlob], '.pcm', { type: 'audio/pcm' });
    const audioFilemp3 = new File([recBlob], '.mp3', { type: 'audio/mp3' });
    postFileUpload({}, {}, audioFilepcm).then((res) => {
      if (res.success) {
        if (res.data && res.data !== null) {
          if (res.data.url && res.data.url !== null) {
            pcmurl.value = res.data.url;
          }
        }
      }
      else {
        message.error(res.msg);
      }
    });
    setTimeout(() => {
      postFileUpload({}, {}, audioFilemp3).then((res) => {
        if (res.data && res.data !== null) {
          if (res.data.url && res.data.url !== null) {
            mp3url.value = res.data.url;
          }
        }
      });
    }, 500);
    // 上传成功才能添加
    setTimeout(() => {
      if (mp3url.value && pcmurl.value) {
        const data = {
          dockSn: dockSn.value,
          contentType: 1, // 0-文本,1音频
          contentUrl: mp3url.value, // 文本文字或喊话url
          contentAuditionUrl: pcmurl.value, // 音频喊话pcm格式的url
        };
        postApiSpeakerAddContent(data).then((res) => {
          if (res.success) {
            pcmurl.value = '';
            mp3url.value = '';
            playing.value = false;
            isEnd.value = false;
            emit('updata');
            message.success(res.msg);
          }
          else {
            message.error(res.msg);
          }
        });
      }
    }, 1500);
  }
}
// 本地播放录音
function recPlay() {
  // 本地播放录音试听,可以直接用URL把blob转换成本地播放地址,用audio进行播放
  const localUrl = URL.createObjectURL(recBlob);
  const audio = document.createElement('audio');
  audio.controls = true;
  document.body.appendChild(audio);
  audio.src = localUrl;
  audio.style.display = 'none';
  audio.play(); // 这样就能播放了
  // 注意不用了时需要revokeObjectURL,否则霸占内存
  setTimeout(() => {
    URL.revokeObjectURL(audio.src);
  }, 5000);
}
</script>

<template>
  <div class="flex items-center" style="flex-direction: column;">
    <div class="mt-2 mb-2">
      {{ playing ? '录音中' : '' }}
    </div>
    <div class="icon-box text-center mt-2">
      <div class="cursor-pointer w-full h-full flex items-center justify-center" :class="playing ? 'active' : ''">
        <PubSvgIcon v-if="!playing" name="icon_vovice" color="#fff" :size="24" />
        <PubSvgIcon v-else name="icon_vovice_active" color="#fff" :size="24" />
      </div>
    </div>
    <div class="flex mt-2">
      <a-button v-if="!playing" type="primary" class="mr-2" @click="recOpen">
        开始录音
      </a-button>
      <a-button v-if="playing" type="primary" class="mr-2" @click="recStop">
        结束录音
      </a-button>
      <a-button v-if="isEnd && !playing" type="primary" class="mr-2" @click="recPlay">
        本地试听
      </a-button>
      <a-button v-if="isEnd && !playing" type="primary" @click="submit">
        上传录音
      </a-button>
    </div>

    <!-- 波形绘制区域 -->
    <div v-show="playing" class="pt-8">
      <div style="display: inline-block; vertical-align: bottom;">
        <div ref="recwave" style="width: 10rem;height: 6rem;" />
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.icon-box {
  flex-direction: column;
  width: 68px;
  height: 68px;
  margin-bottom: 6px;
  background: url("@/assets/images/airportDetail/nav/nav-bg0.png") no-repeat center center;
  background-size: 100% 100%;

  .active {
    border-radius: 50%;
    box-shadow: 0 0 4px 0 #00abff;
  }
}
</style>

<!-- 录音 -->
<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { useStorage } from '@vueuse/core';
// 必须引入的核心
import Recorder from 'recorder-core';
import 'recorder-core/src/engine/mp3';
import 'recorder-core/src/engine/mp3-engine';
import 'recorder-core/src/engine/wav';
import 'recorder-core/src/engine/pcm';
import 'recorder-core/src/extensions/waveview';

const CURRENT_EQIUP: any = useStorage('CURRENT_EQUIP', {}, sessionStorage);
const dockSn = computed(() => CURRENT_EQIUP.value.dock || '');
let rec: any;
let recBlob: any;
let wave: any;
const recwave = ref(null);
// 打开录音
function recOpen() {
  // 创建录音对象
  rec = Recorder({
    type: 'pcm', // 录音格式,可以换成wav等其他格式
    sampleRate: 16000, // 录音的采样率,越大细节越丰富越细腻
    bitRate: 16, // 录音的比特率,越大音质越好
    format: 'pcm',
    onProcess: (
      buffers: any,
      powerLevel: any,
      // bufferDuration: any,
      bufferSampleRate: any,
      // newBufferIdx: any,
      // asyncEnd: any,
    ) => {
      // 录音实时回调,大约1秒调用12次本回调
      // 可实时绘制波形,实时上传(发送)数据
      if (wave) {
        wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
      }
    },
  });
  if (!rec) {
    // alert('当前浏览器不支持录音功能!');
    return;
  }
  // 打开录音,获得权限
  rec.open(
    () => {
      // console.log('录音已打开');
      if (recwave.value) {
        // 创建音频可视化图形绘制对象
        wave = Recorder.WaveView({ elem: recwave.value });
        recStart();
        // setTimeout(recStart, 1500);
      }
    },
    (msg: any, isUserNotAllow: any) => {
      // 用户拒绝了录音权限,或者浏览器不支持录音
      console.log(`${isUserNotAllow ? 'UserNotAllow,' : ''}无法录音:${msg}`);
    },
  );
}
// 开始录音
function recStart() {
  if (!rec) {
    console.error('未打开录音');
    return;
  }
  rec.start();
  console.log('已开始录音');
}
// 结束录音
function recStop() {
  if (!rec) {
    console.error('未打开录音');
    return;
  }
  rec.stop(
    (blob: any, duration: any) => {
      // blob就是我们要的录音文件对象,可以上传,或者本地播放
      recBlob = blob;
      // 简单利用URL生成本地文件地址,此地址只能本地使用,比如赋值给audio.src进行播放,赋值给a.href然后a.click()进行下载(a需提供download="xxx.mp3"属性)
      const localUrl = (window.URL || window.webkitURL).createObjectURL(blob);
      console.log('录音成功', blob, localUrl, `时长:${duration}ms`);
      // console.log('recBlob1111', recBlob);
      rec.close(); // 关闭录音,释放录音资源,当然可以不释放,后面可以连续调用start
      rec = null;
    },
    (err: any) => {
      console.error(`结束录音出错:${err}`);
      rec.close(); // 关闭录音,释放录音资源,当然可以不释放,后面可以连续调用start
      rec = null;
    },
  );
}
// 上传录音
function submit() {
  if (recBlob) {
    const audioFile = new File([recBlob], '.pcm', { type: 'audio/pcm' });
    postFileUpload({}, {}, audioFile).then((res) => {
      if (res.success) {
        if (res.data && res.data !== null) {
          if (res.data.url && res.data.url !== null) {
            // 实时喊话
            postApiSpeakerShoutNow({
              dockSn: dockSn.value,
              contentUrl: res.data.url,
            }).then((res) => {
              if (res.success) {
                message.success('喊话成功');
              }
              else {
                message.error(res.msg);
              }
            });
          }
        }
      }
    });
  }
}
// 本地播放录音
// function recPlay() {
//   // 本地播放录音试听,可以直接用URL把blob转换成本地播放地址,用audio进行播放
//   const localUrl = URL.createObjectURL(recBlob);
//   const audio = document.createElement('audio');
//   audio.controls = true;
//   document.body.appendChild(audio);
//   audio.src = localUrl;
//   audio.style.display = 'none';
//   audio.play(); // 这样就能播放了
//   // 注意不用了时需要revokeObjectURL,否则霸占内存
//   setTimeout(() => {
//     URL.revokeObjectURL(audio.src);
//   }, 5000);
// }
const isvovice = ref(false);
// function changeVovice() {
//   isvovice.value = !isvovice.value;
//   if (isvovice.value) {
//     // 开始录音
//     recOpen();
//   }
//   else {
//     // 结束录音
//     recStop();
//     // 上传录音
//     setTimeout(() => {
//       submit();
//     }, 1000);
//   }
// }
const longPressActive = ref(false);
function emitMessage() {
  // 发送你的消息
  // console.log('长按了');
  isvovice.value = true;
  recOpen();
}

function startLongPress(event: any) {
  // 阻止默认上下文菜单出现
  event.preventDefault();
  longPressActive.value = true;
  const timeout = setTimeout(() => {
    emitMessage();
  }, 300); // 设置长按1秒触发
  // 存储setTimeout的引用,以便稍后清理
  event.target.timeout = timeout;
}

function endLongPress(event: any) {
  longPressActive.value = false;
  // 清理setTimeout的引用
  if (event.target.timeout) {
    clearTimeout(event.target.timeout);
  }
  // console.log('取消了');
  isvovice.value = false;
  recBlob = null;
  // 结束录音
  recStop();
  // 上传录音
  setTimeout(() => {
    submit();
  }, 1000);
}

onMounted(() => {
  // 监听全局mouseup事件以确保即使鼠标移出元素也能正确清理
  // document.addEventListener('mouseup', endLongPress);
});

onUnmounted(() => {
  // 清理事件监听器
  document.removeEventListener('mouseup', endLongPress);
});
</script>

<template>
  <!-- 长按触发 -->
  <div class="icon-box text-center" @mousedown="startLongPress" @mouseup="endLongPress">
    <div class="cursor-pointer w-full h-full flex items-center justify-center" :class="isvovice ? 'active' : ''">
      <PubSvgIcon v-if="!isvovice" name="icon_vovice" color="#fff" :size="24" />
      <PubSvgIcon v-else name="icon_vovice_active" color="#fff" :size="24" />
    </div>
    <div class="mt-2">
      {{ isvovice ? '喊话中' : '实时喊话' }}
    </div>
  </div>
  <!-- 点击触发 -->
  <!-- <div class="icon-box text-center" @click="changeVovice">
      <div class="cursor-pointer w-full h-full flex items-center justify-center" :class="isvovice ? 'active' : ''">
        <PubSvgIcon v-if="!isvovice" name="icon_vovice" color="#fff" :size="24" />
        <PubSvgIcon v-else name="icon_vovice_active" color="#fff" :size="24" />
      </div>
      <div class="mt-2">
        {{ isvovice ? '喊话中' : '实时喊话' }}
      </div>
    </div> -->
  <!-- 波形绘制区域 -->
  <div v-if="isvovice" class="pt-8">
    <div style="display: inline-block; vertical-align: bottom;">
      <div ref="recwave" style="width: 10rem;height: 6rem;" />
    </div>
  </div>
  <!-- <a-button type="primary" class="mr-2" @click="recOpen">
        开始录音
      </a-button>
      <a-button type="primary" class="mr-2" @click="recStop">
        结束录音
      </a-button>
      <a-button type="primary" @click="recPlay">
        本地试听
      </a-button>
      <a-button type="primary" class="mt-2" @click="submit">
        上传录音
      </a-button> -->
</template>

<style lang="less" scoped>
.icon-box {
  flex-direction: column;
  width: 68px;
  height: 68px;
  margin-bottom: 6px;
  background: url("@/assets/images/airportDetail/nav/nav-bg0.png") no-repeat center center;
  background-size: 100% 100%;

  .active {
    border-radius: 50%;
    box-shadow: 0 0 4px 0 #00abff;
  }
}
</style>

 



精彩评论(0)

0 0 举报