当前位置:   article > 正文

vue2(H5录音)_vue2录音插件

vue2录音插件
  1. <template>
  2. <div class="upload_container">
  3. <div class="igmBox">
  4. <div class="img_box" v-for="(item, index) in data" :key="item.url">
  5. <img :src="getFileUrl(item)" @click="handleclicksc(index)">
  6. <div class="delete_btn"> <van-icon name="cross" class="" @click="afterDelete(index)" /> </div>
  7. </div>
  8. </div>
  9. <van-uploader v-bind="uploadParameter" :after-read="afterRead" :before-read="beforeRead" @oversize="onOversize"
  10. upload-icon="plus" :preview-full-image="false" v-show="isUploadShow" ref="uploadRef">
  11. <template #default>
  12. <div></div>
  13. </template>
  14. </van-uploader>
  15. <div class="uploadBox">
  16. <div @click="$refs.uploadRef.chooseFile()" class="chooseFile"><i
  17. class="iconfont icon-zengjia"></i><span>图片/视频</span></div>
  18. <div v-longpress="handleAudio" class="chooseFile" @touchend="touchendBtn"><i
  19. class="iconfont icon-zengjia"></i><span>语音输入</span></div>
  20. <!-- 语音音阶动画 -->
  21. <div class="prompt-layer prompt-layer-1" v-if="isLongPress">
  22. <div class="prompt-loader">
  23. <div class="em" v-for="(item, index) in 15" :key="index"></div>
  24. </div>
  25. <!-- <div class="p">{{ '剩余:' + count + 's' }}</div> -->
  26. <div class="span">松手结束录音</div>
  27. </div>
  28. </div>
  29. <PreviewImgOrVideo :previewShow="previewShow" @close="$event => previewShow = $event" :data="preViewData"
  30. v-if="previewShow">
  31. </PreviewImgOrVideo>
  32. </div>
  33. </template>
  34. <script>
  35. import { mapGetters } from "vuex";
  36. import videoIcon from '../images/video_icon.png';
  37. import audioIcon from '../images/audio_icon.png';
  38. import { getNewFileUrl } from "@/utils/download";
  39. import { filesExperts } from '@/api/appyjzj/newIndex';
  40. import PreviewImgOrVideo from './previewImgOrVideo.vue';
  41. export default {
  42. directives: {
  43. longpress: {
  44. bind: function (el, binding) {
  45. // 定义长按时间阈值
  46. let pressTimer = null
  47. let longPressActive = false
  48. // 创建计时器(这会在长按后启动)
  49. let start = (e) => {
  50. if (e.type === 'click' && e.button !== 0) {
  51. return
  52. }
  53. if (pressTimer === null) {
  54. pressTimer = setTimeout(() => {
  55. handler()
  56. }, 500)
  57. }
  58. }
  59. // 取消计时器(长按结束时会运行)
  60. let cancel = (e) => {
  61. if (pressTimer !== null) {
  62. clearTimeout(pressTimer)
  63. pressTimer = null
  64. }
  65. }
  66. // 长按要运行的函数
  67. const handler = (e) => {
  68. binding.value(e)
  69. }
  70. // 添加事件监听器
  71. el.addEventListener("mousedown", start)
  72. el.addEventListener("touchstart", start)
  73. // 取消计时器
  74. el.addEventListener("click", cancel)
  75. el.addEventListener("mouseout", cancel)
  76. el.addEventListener("touchend", cancel)
  77. el.addEventListener("touchcancel", cancel)
  78. }
  79. }
  80. },
  81. components: {
  82. PreviewImgOrVideo
  83. },
  84. props: {
  85. data: {
  86. type: Array,
  87. default: () => []
  88. },
  89. uploadParameter: {
  90. type: Object,
  91. default: () => { }
  92. }
  93. },
  94. model: {
  95. prop: 'data',
  96. event: 'change'
  97. },
  98. data() {
  99. return {
  100. previewShow: false,
  101. preViewData: {},
  102. loading: false,
  103. mediaRecorder: null,
  104. isLongPress: false,
  105. recordedBlob: false
  106. };
  107. },
  108. computed: {
  109. ...mapGetters(["userInfo"]),
  110. isUploadShow() {
  111. return this.uploadParameter['max-count'] && this.uploadParameter['max-count'] > this.data.length
  112. }
  113. },
  114. methods: {
  115. getNewFileUrl,
  116. isVideo(filename) {
  117. const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'MOV'];
  118. const extension = filename.split('.').pop();
  119. return videoExtensions.includes(extension);
  120. },
  121. isAudio(filename) {
  122. const audioExtensions = ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac'];
  123. const extension = filename.split('.').pop();
  124. return audioExtensions.includes(extension);
  125. },
  126. getFileUrl(file) {
  127. if (this.isVideo(file.name)) {
  128. return videoIcon;
  129. } else if (this.isAudio(file.name)) {
  130. return audioIcon;
  131. } else {
  132. return this.getNewFileUrl(file.url);
  133. }
  134. },
  135. // 上传附件
  136. async afterRead(file) {
  137. this.loading = true
  138. file.status = "uploading";
  139. file.message = "上传中...";
  140. const formData = new FormData();
  141. formData.append("singleFile", file.file);
  142. try {
  143. const { data, code } = await filesExperts(formData);
  144. if (code === 2000) {
  145. this.data.push({
  146. name: data.fileName,
  147. uid: data.id,
  148. url: data.location
  149. });
  150. file.status = "done";
  151. file.message = "上传成功";
  152. console.log('this.from', this.formData)
  153. }
  154. } catch (err) {
  155. console.log('err', err)
  156. file.status = "failed";
  157. file.message = "上传失败";
  158. } finally {
  159. this.loading = false
  160. }
  161. },
  162. beforeRead(file) {
  163. if (this.loading) return false
  164. if (!file.type.startsWith('image/') && !file.type.startsWith('video/')) {
  165. this.$toast('请上传图片或视频文件');
  166. return false;
  167. }
  168. return true;
  169. },
  170. //手动点击删除,修改包含所有信息的文件列表,通过watch根据该列表动态修改图片文件列表
  171. afterDelete(index) {
  172. this.data.splice(index, 1)
  173. },
  174. onOversize(file) {
  175. this.$toast('超过大小');
  176. },
  177. //取消掉组件自带的点击预览功能,自己添加(系统自带预览点击视频时会先视频的播放图片)
  178. handleclicksc(index) {
  179. let file = this.data[index]
  180. const name = file.name;
  181. const fileExtension = name.split(".").pop().toLowerCase();
  182. const isImage = ["jpg", "jpeg", "png", "gif", "bmp"].includes(fileExtension);
  183. const isVideo = ["mp4", "mov", "avi", "mkv", 'MOV'].includes(fileExtension);
  184. if (isVideo) {
  185. this.preViewData = {
  186. url: getNewFileUrl(file.url),
  187. urlType: 'video'
  188. }
  189. }
  190. if (isImage) {
  191. this.preViewData = {
  192. url: getNewFileUrl(file.url),
  193. urlType: 'image'
  194. }
  195. }
  196. this.previewShow = true
  197. },
  198. async handleAudio() {
  199. this.isLongPress = true // 开始长按
  200. // 开始录音
  201. if (this.isLongPress) {
  202. console.log('navigator', navigator)
  203. console.log('navigator.mediaDevices', navigator.mediaDevices)
  204. navigator.mediaDevices.getUserMedia({ audio: true })
  205. .then(stream => {
  206. this.mediaRecorder = new MediaRecorder(stream);
  207. this.mediaRecorder.addEventListener('dataavailable', event => {
  208. console.log('event', event.data)
  209. this.recordedBlob = event.data;
  210. });
  211. this.mediaRecorder.start();
  212. })
  213. .catch(error => {
  214. // 用户拒绝访问麦克风
  215. });
  216. }
  217. },
  218. async touchendBtn() {
  219. this.isLongPress = false // 结束长按
  220. this.mediaRecorder.stop();
  221. this.mediaRecorder.addEventListener('stop', async () => {
  222. // dataavailable 会在这里被触发
  223. try {
  224. const formData = new FormData();
  225. formData.append("singleFile", this.recordedBlob, `${this.userInfo.profile.name}的录音.mp3`);
  226. const { data, code } = await filesExperts(formData);
  227. if (code === 2000) {
  228. this.data.push({
  229. name: data.fileName,
  230. uid: data.id,
  231. url: data.location
  232. });
  233. }
  234. } catch (err) {
  235. console.log('err', err)
  236. } finally {
  237. this.loading = false
  238. this.mediaRecorder = null;
  239. }
  240. })
  241. }
  242. }
  243. }
  244. </script>
  245. <style scoped lang="scss">
  246. .upload_container {
  247. margin-top: 7px;
  248. .igmBox {
  249. display: grid;
  250. grid-template-columns: repeat(auto-fill, 80px);
  251. grid-gap: 10px;
  252. }
  253. width: 100%;
  254. }
  255. .img_box {
  256. width: 80px;
  257. height: 80px;
  258. position: relative;
  259. img {
  260. width: 100%;
  261. height: 100%;
  262. }
  263. .delete_btn {
  264. position: absolute;
  265. top: 0;
  266. right: 0;
  267. width: 14px;
  268. height: 14px;
  269. background-color: rgba(0, 0, 0, 0.7);
  270. border-radius: 0 0 0 12px;
  271. .van-icon-cross {
  272. position: absolute;
  273. top: -2px;
  274. right: -2px;
  275. color: #fff;
  276. font-size: 16px;
  277. -webkit-transform: scale(0.5);
  278. transform: scale(0.5);
  279. }
  280. }
  281. }
  282. .uploadBox {
  283. position: relative;
  284. all: unset;
  285. display: flex;
  286. justify-content: flex-end;
  287. .chooseFile {
  288. margin-left: 16px;
  289. i {
  290. margin-right: 4px;
  291. font-size: 16px;
  292. color: #1890FF;
  293. }
  294. span {
  295. font-size: 13px;
  296. font-family: PingFangSC-Regular, PingFang SC;
  297. color: #1890FF;
  298. }
  299. }
  300. /* 提示小弹窗 */
  301. .prompt-layer {
  302. border-radius: 8px;
  303. background: #f6f6f6;
  304. padding: 8px 16px;
  305. box-sizing: border-box;
  306. position: absolute;
  307. left: 50%;
  308. top: 50%;
  309. z-index: 999;
  310. transform: translate(-50%, -50%);
  311. // box-shadow: 5px rgba(0, 0, 0, .5);
  312. }
  313. // .prompt-layer::after {
  314. // content: '';
  315. // display: block;
  316. // border: 6px solid rgba(0, 0, 0, 0);
  317. // border-top-color: rgba(255, 211, 0, 1);
  318. // position: absolute;
  319. // bottom: -10px;
  320. // left: 50%;
  321. // transform: translateX(-50%);
  322. // }
  323. .prompt-layer-1 {
  324. font-size: 12px;
  325. width: 128px;
  326. text-align: center;
  327. display: flex;
  328. flex-direction: column;
  329. align-items: center;
  330. justify-content: center;
  331. }
  332. .prompt-layer-1 .p {
  333. color: #000000;
  334. }
  335. .prompt-layer-1 .span {
  336. color: rgba(0, 0, 0, .6);
  337. }
  338. }
  339. /* 语音音阶------------- */
  340. .prompt-loader {
  341. width: 96px;
  342. height: 20px;
  343. display: flex;
  344. align-items: center;
  345. justify-content: space-between;
  346. margin-bottom: 6px;
  347. }
  348. .prompt-loader .em {
  349. display: block;
  350. background: #333333;
  351. width: 1px;
  352. height: 10%;
  353. margin-right: 2.5px;
  354. float: left;
  355. }
  356. .prompt-loader .em:last-child {
  357. margin-right: 0px;
  358. }
  359. .prompt-loader .em:nth-child(1) {
  360. animation: load 2.5s 1.4s infinite linear;
  361. }
  362. .prompt-loader .em:nth-child(2) {
  363. animation: load 2.5s 1.2s infinite linear;
  364. }
  365. .prompt-loader .em:nth-child(3) {
  366. animation: load 2.5s 1s infinite linear;
  367. }
  368. .prompt-loader .em:nth-child(4) {
  369. animation: load 2.5s 0.8s infinite linear;
  370. }
  371. .prompt-loader .em:nth-child(5) {
  372. animation: load 2.5s 0.6s infinite linear;
  373. }
  374. .prompt-loader .em:nth-child(6) {
  375. animation: load 2.5s 0.4s infinite linear;
  376. }
  377. .prompt-loader .em:nth-child(7) {
  378. animation: load 2.5s 0.2s infinite linear;
  379. }
  380. .prompt-loader .em:nth-child(8) {
  381. animation: load 2.5s 0s infinite linear;
  382. }
  383. .prompt-loader .em:nth-child(9) {
  384. animation: load 2.5s 0.2s infinite linear;
  385. }
  386. .prompt-loader .em:nth-child(10) {
  387. animation: load 2.5s 0.4s infinite linear;
  388. }
  389. .prompt-loader .em:nth-child(11) {
  390. animation: load 2.5s 0.6s infinite linear;
  391. }
  392. .prompt-loader .em:nth-child(12) {
  393. animation: load 2.5s 0.8s infinite linear;
  394. }
  395. .prompt-loader .em:nth-child(13) {
  396. animation: load 2.5s 1s infinite linear;
  397. }
  398. .prompt-loader .em:nth-child(14) {
  399. animation: load 2.5s 1.2s infinite linear;
  400. }
  401. .prompt-loader .em:nth-child(15) {
  402. animation: load 2.5s 1.4s infinite linear;
  403. }
  404. @keyframes load {
  405. 0% {
  406. height: 10%;
  407. }
  408. 50% {
  409. height: 100%;
  410. }
  411. 100% {
  412. height: 10%;
  413. }
  414. }
  415. /* 语音音阶-------------------- */
  416. .prompt-layer-2 {
  417. top: -40px;
  418. }
  419. .prompt-layer-2 .text {
  420. color: rgba(0, 0, 0, 1);
  421. font-size: 12px;
  422. }
  423. /deep/.van-uploader {
  424. width: 100%;
  425. .van-uploader__wrapper,
  426. .van-uploader__input-wrapper {
  427. width: 100%;
  428. }
  429. }
  430. </style>

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号