Commit 94178998e541d4c829294a1b2dfab5f68e5e2c18
1 parent
8a5db8c1
mp:前端 message 增加 voice 语音的播放
Showing
5 changed files
with
116 additions
and
2 deletions
yudao-ui-admin/package.json
| @@ -43,6 +43,7 @@ | @@ -43,6 +43,7 @@ | ||
| 43 | "@babel/parser": "7.18.4", | 43 | "@babel/parser": "7.18.4", |
| 44 | "@riophae/vue-treeselect": "0.4.0", | 44 | "@riophae/vue-treeselect": "0.4.0", |
| 45 | "axios": "0.27.2", | 45 | "axios": "0.27.2", |
| 46 | + "benz-amr-recorder": "^1.1.5", | ||
| 46 | "bpmn-js-token-simulation": "0.10.0", | 47 | "bpmn-js-token-simulation": "0.10.0", |
| 47 | "clipboard": "2.0.8", | 48 | "clipboard": "2.0.8", |
| 48 | "core-js": "^3.26.0", | 49 | "core-js": "^3.26.0", |
yudao-ui-admin/src/views/mp/components/wx-video-play/main.vue
| 1 | <!-- | 1 | <!-- |
| 2 | - Copyright (C) 2018-2019 | 2 | - Copyright (C) 2018-2019 |
| 3 | - All rights reserved, Designed By www.joolun.com | 3 | - All rights reserved, Designed By www.joolun.com |
| 4 | - 芋艿: | 4 | + 芋道源码: |
| 5 | ① bug 修复: | 5 | ① bug 修复: |
| 6 | 1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; | 6 | 1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; |
| 7 | 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 | 7 | 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 |
yudao-ui-admin/src/views/mp/components/wx-voice-play/main.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + - Copyright (C) 2018-2019 | ||
| 3 | + - All rights reserved, Designed By www.joolun.com | ||
| 4 | + 芋道源码: | ||
| 5 | + ① bug 修复: | ||
| 6 | + 1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; | ||
| 7 | + 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 | ||
| 8 | + 2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 | ||
| 9 | + ② 代码优化:将 props 中的 objData 调成为 data 中对应的属性,并补充相关注释 | ||
| 10 | +--> | ||
| 11 | +<template> | ||
| 12 | + <div class="wx-voice-div" @click="playVoice"> | ||
| 13 | + <i :class="playing !== true ? 'el-icon-video-play': 'el-icon-video-pause'"> | ||
| 14 | + <span class="amr-duration" v-if="duration">{{ duration }}</span> | ||
| 15 | + </i> | ||
| 16 | + <div v-if="content"> | ||
| 17 | + <el-tag type="success" size="mini">语音识别</el-tag> | ||
| 18 | + {{ content }} | ||
| 19 | + </div> | ||
| 20 | + </div> | ||
| 21 | +</template> | ||
| 22 | + | ||
| 23 | +<script> | ||
| 24 | +// 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder | ||
| 25 | +const BenzAMRRecorder = require('benz-amr-recorder') | ||
| 26 | + | ||
| 27 | +export default { | ||
| 28 | + name: "wxVoicePlayer", | ||
| 29 | + props: { | ||
| 30 | + url: { // 语音地址,例如说:https://www.iocoder.cn/xxx.amr | ||
| 31 | + type: String, | ||
| 32 | + required: true | ||
| 33 | + }, | ||
| 34 | + content: { // 语音文本 | ||
| 35 | + type: String, | ||
| 36 | + required: false | ||
| 37 | + } | ||
| 38 | + }, | ||
| 39 | + data() { | ||
| 40 | + return { | ||
| 41 | + amr: undefined, // BenzAMRRecorder 对象 | ||
| 42 | + playing: false, // 是否在播放中 | ||
| 43 | + duration: undefined, // 播放时长 | ||
| 44 | + } | ||
| 45 | + }, | ||
| 46 | + methods:{ | ||
| 47 | + playVoice() { | ||
| 48 | + debugger | ||
| 49 | + // 情况一:未初始化,则创建 BenzAMRRecorder | ||
| 50 | + if (this.amr === undefined){ | ||
| 51 | + this.amrInit(); | ||
| 52 | + return; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + if (this.amr.isPlaying()) { | ||
| 56 | + this.amrStop(); | ||
| 57 | + } else { | ||
| 58 | + this.amrPlay(); | ||
| 59 | + } | ||
| 60 | + }, | ||
| 61 | + amrInit() { | ||
| 62 | + const amr = new BenzAMRRecorder(); | ||
| 63 | + this.amr = amr; | ||
| 64 | + // 设置播放 | ||
| 65 | + const that = this | ||
| 66 | + amr.initWithUrl(this.url).then(function() { | ||
| 67 | + that.amrPlay() | ||
| 68 | + that.duration = amr.getDuration(); | ||
| 69 | + }) | ||
| 70 | + // 监听暂停 | ||
| 71 | + amr.onEnded(function() { | ||
| 72 | + that.playing = false; | ||
| 73 | + }) | ||
| 74 | + }, | ||
| 75 | + amrPlay() { | ||
| 76 | + this.playing = true; | ||
| 77 | + this.amr.play() | ||
| 78 | + }, | ||
| 79 | + amrStop() { | ||
| 80 | + this.playing = false; | ||
| 81 | + this.amr.stop() | ||
| 82 | + }, | ||
| 83 | + } | ||
| 84 | +}; | ||
| 85 | +</script> | ||
| 86 | + | ||
| 87 | +<style lang="scss" scoped> | ||
| 88 | + .wx-voice-div { | ||
| 89 | + padding: 5px; | ||
| 90 | + background-color: #eaeaea; | ||
| 91 | + border-radius: 10px; | ||
| 92 | + } | ||
| 93 | + .amr-duration { | ||
| 94 | + font-size: 11px; | ||
| 95 | + margin-left: 5px; | ||
| 96 | + } | ||
| 97 | +</style> |
yudao-ui-admin/src/views/mp/message/index.vue
| @@ -70,7 +70,9 @@ | @@ -70,7 +70,9 @@ | ||
| 70 | </div> | 70 | </div> |
| 71 | <!-- 【消息】区域 --> | 71 | <!-- 【消息】区域 --> |
| 72 | <div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div> | 72 | <div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div> |
| 73 | - <!-- TODO 语音 --> | 73 | + <div v-else-if="scope.row.type === 'voice'"> |
| 74 | + <wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" /> | ||
| 75 | + </div> | ||
| 74 | <div v-else-if="scope.row.type === 'image'"> | 76 | <div v-else-if="scope.row.type === 'image'"> |
| 75 | <a target="_blank" :href="scope.row.mediaUrl"> | 77 | <a target="_blank" :href="scope.row.mediaUrl"> |
| 76 | <img :src="scope.row.mediaUrl" style="width: 100px"> | 78 | <img :src="scope.row.mediaUrl" style="width: 100px"> |
| @@ -154,12 +156,14 @@ import { | @@ -154,12 +156,14 @@ import { | ||
| 154 | } from "@/api/mp/message"; | 156 | } from "@/api/mp/message"; |
| 155 | import Editor from '@/components/Editor/index.vue'; | 157 | import Editor from '@/components/Editor/index.vue'; |
| 156 | import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'; | 158 | import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'; |
| 159 | +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'; | ||
| 157 | 160 | ||
| 158 | export default { | 161 | export default { |
| 159 | name: "WxFansMsg", | 162 | name: "WxFansMsg", |
| 160 | components: { | 163 | components: { |
| 161 | Editor, | 164 | Editor, |
| 162 | WxVideoPlayer, | 165 | WxVideoPlayer, |
| 166 | + WxVoicePlayer | ||
| 163 | }, | 167 | }, |
| 164 | data() { | 168 | data() { |
| 165 | return { | 169 | return { |
yudao-ui-admin/yarn.lock
| @@ -2327,6 +2327,18 @@ | @@ -2327,6 +2327,18 @@ | ||
| 2327 | dependencies: | 2327 | dependencies: |
| 2328 | "tweetnacl" "^0.14.3" | 2328 | "tweetnacl" "^0.14.3" |
| 2329 | 2329 | ||
| 2330 | +"benz-amr-recorder@^1.1.5": | ||
| 2331 | + "integrity" "sha512-NepctcNTsZHK8NxBb5uKO5p8S+xkbm+vD6GLSkCYdJeEsriexvgumLHpDkanX4QJBcLRMVtg16buWMs+gUPB3g==" | ||
| 2332 | + "resolved" "https://registry.npmmirror.com/benz-amr-recorder/-/benz-amr-recorder-1.1.5.tgz" | ||
| 2333 | + "version" "1.1.5" | ||
| 2334 | + dependencies: | ||
| 2335 | + "benz-recorderjs" "^1.0.5" | ||
| 2336 | + | ||
| 2337 | +"benz-recorderjs@^1.0.5": | ||
| 2338 | + "integrity" "sha512-EwedOQo9KLti7HxDi/eZY51PSRbAXnOdEZmLvJ6ro3QQSoF9Y3AXBt57MIllGvVz5vtFYMeikG+GD7qTm3+p9w==" | ||
| 2339 | + "resolved" "https://registry.npmmirror.com/benz-recorderjs/-/benz-recorderjs-1.0.5.tgz" | ||
| 2340 | + "version" "1.0.5" | ||
| 2341 | + | ||
| 2330 | "bfj@^6.1.1": | 2342 | "bfj@^6.1.1": |
| 2331 | "integrity" "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==" | 2343 | "integrity" "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==" |
| 2332 | "resolved" "https://registry.npmmirror.com/bfj/-/bfj-6.1.2.tgz" | 2344 | "resolved" "https://registry.npmmirror.com/bfj/-/bfj-6.1.2.tgz" |