CustomRecorder.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. <template>
  2. <view class="custom-recorder">
  3. <view class="record-file-list" v-if="list.length">
  4. <view class="record-file-list-item" v-for="(item, index) in list" :key="index">
  5. <view class="close-btn" @tap="handleDel(item)"></view>
  6. <view class="play-icon" v-if="playStatus==='stop'">
  7. <image style="width: 100%; height: 100%;" src="/static/images/components/recorder/play.svg"
  8. mode="scaleToFill"></image>
  9. </view>
  10. <view class="play-icon" v-else>
  11. <image style="width: 100%; height: 100%;" src="/static/images/components/recorder/stop.svg"
  12. mode="scaleToFill"></image>
  13. </view>
  14. <view class="item-info">
  15. <view class="top-name">
  16. {{item.name}}
  17. </view>
  18. <view class="bottom-content">
  19. <view class="base-info" v-if="playStatus==='stop'">
  20. <view class="icon">
  21. <image style="width: 100%; height: 100%;"
  22. src="/static/images/components/recorder/voice-short.svg" mode="scaleToFill"></image>
  23. </view>
  24. <view class="duration">
  25. 音频时长:{{ item.duration }}
  26. </view>
  27. </view>
  28. <view class="progress" v-else>
  29. <uv-slider v-model="slider.value" block-size="12" block-color="#FF7300"
  30. backgroundColor="#ffb97f" activeColor="#ff7300" :min="slider.min" :max="slider.max"
  31. :step="slider.step" disabled>
  32. </uv-slider>
  33. <view class="duration">
  34. {{ item.duration }}
  35. </view>
  36. </view>
  37. </view>
  38. </view>
  39. </view>
  40. </view>
  41. <uni-popup ref="popup" type="bottom" class="pop-up-box">
  42. <view class="pop-up-content">
  43. <view class="content-header">
  44. 音频录音
  45. </view>
  46. <view class="content-body">
  47. <view class="content-body-duration">
  48. {{duration}}
  49. </view>
  50. <view class="icon">
  51. <image style="width: 100%; height: 100%;" src="/static/images/components/recorder/voice.svg"
  52. mode="scaleToFill"></image>
  53. </view>
  54. <view class="btn">
  55. <button style="height: 68rpx;line-height: 68rpx;border-radius: 1998rpx;" type="primary"
  56. @longpress="handleStart" @touchcancel="handleStop"
  57. @touchend="handleStop">{{btnText[recordStatus]}}</button>
  58. </view>
  59. </view>
  60. </view>
  61. </uni-popup>
  62. <custom-modal ref="modalRef"></custom-modal>
  63. </view>
  64. </template>
  65. <script>
  66. import moment from 'moment';
  67. import CustomModal from '../Modal/CustomModal.vue';
  68. // #ifdef APP-PLUS
  69. const recorder = plus.audio.getRecorder();
  70. const player = plus.audio.createPlayer({
  71. autoplay: false
  72. });
  73. // #endif
  74. export default {
  75. components: {
  76. CustomModal
  77. },
  78. data() {
  79. return {
  80. list: [],
  81. duration: "00:00:00",
  82. recordStatus: "stop",
  83. btnText: {
  84. stop: "按住说话",
  85. start: "说完了"
  86. },
  87. voicePath: '',
  88. playStatus: 'stop',
  89. slider: {
  90. value: 0,
  91. min: 0,
  92. max: 100,
  93. step: 1
  94. },
  95. }
  96. },
  97. onLoad() {},
  98. beforeDestroy() {
  99. player.destroy(); // 释放音频资源
  100. },
  101. methods: {
  102. startRecord() {
  103. console.log('开始录音');
  104. // #ifdef APP-PLUS
  105. console.log('走的app')
  106. recorder.record({
  107. format: 'amr',
  108. filename: "_doc/hlt_app/录音文件"
  109. },
  110. async (path) => {
  111. console.log('录制成功')
  112. // 此处就要上传这个文件
  113. uni.showLoading({
  114. title: '上传中...'
  115. })
  116. setTimeout(() => {
  117. uni.hideLoading()
  118. // 上传成功后,生成一条记录
  119. this.voicePath = path
  120. console.log(this.voicePath)
  121. plus.io.getAudioInfo({
  122. filePath: path,
  123. success: ({
  124. duration
  125. }) => {
  126. console.log('时长', duration)
  127. list.push({
  128. path,
  129. duration
  130. })
  131. }
  132. })
  133. }, 1500)
  134. },
  135. (err) => {
  136. uni.$msg(err)
  137. }
  138. )
  139. // #endif
  140. },
  141. // 结束录音
  142. endRecord() {
  143. console.log('录音结束');
  144. // recorder.stop();
  145. this.list.push({
  146. name: moment().format('YYYY-MM-DD HH:mm:ss'),
  147. path: "../",
  148. duration: this.duration
  149. })
  150. },
  151. // 播放
  152. playVoice() {
  153. console.log('播放录音');
  154. if (this.voicePath) {
  155. // #ifdef APP-PLUS
  156. player.setStyles({
  157. src: this.voicePath
  158. });
  159. // #endif
  160. player.play()
  161. }
  162. },
  163. // 打开底部弹窗,用于开始录音
  164. handleOpenRecord() {
  165. this.$refs.popup.open()
  166. },
  167. // 按住说话事件
  168. // 开始录音, 并计时
  169. handleStart() {
  170. this.startRecord()
  171. this.recordStatus = 'start'
  172. this.timer && clearInterval(this.timer)
  173. // 计时器内部处理经过时长并显示在面板
  174. let timeStart = 0
  175. this.timer = setInterval(() => {
  176. timeStart += 1000
  177. let sec = this.timeFormat(moment.duration(timeStart).asSeconds() % 60)
  178. let min = this.timeFormat(moment.duration(timeStart).asMinutes() % 60)
  179. let hour = this.timeFormat(moment.duration(timeStart).asHours())
  180. this.duration = `${hour}:${min}:${sec}`
  181. }, 1000)
  182. },
  183. timeFormat(time) {
  184. if (0 <= time && time < 1) {
  185. return '00'
  186. }
  187. if (time >= 1 && time < 10) {
  188. return '0' + Math.floor(time)
  189. } else {
  190. return Math.floor(time)
  191. }
  192. },
  193. // 松开事件
  194. // 结束录音
  195. handleStop() {
  196. this.endRecord()
  197. this.recordStatus = 'stop'
  198. this.resetRecord()
  199. },
  200. resetRecord() {
  201. this.duration = "00:00:00"
  202. this.timer && clearInterval(this.timer)
  203. },
  204. // 删除录音
  205. handleDel(record) {
  206. this.$refs.modalRef.showModal({
  207. title: "确定删除该录音?",
  208. onOk: () => {
  209. this.list.forEach((item, index) => {
  210. if (item.name === record.name) {
  211. this.list.splice(index, 1)
  212. }
  213. })
  214. }
  215. })
  216. }
  217. }
  218. }
  219. </script>
  220. <style lang="scss" scoped>
  221. .custom-recorder {
  222. width: 100%;
  223. .record-file-list {
  224. padding: 20rpx 0;
  225. &-item {
  226. margin-bottom: 20rpx;
  227. border-radius: 12rpx;
  228. background: $uni-bg-color-grey;
  229. padding: 10rpx 20rpx;
  230. display: flex;
  231. align-items: center;
  232. position: relative;
  233. &::after {
  234. content: '';
  235. position: absolute;
  236. top: 0;
  237. right: 0;
  238. width: 68rpx;
  239. height: 68rpx;
  240. background: url('../../static/images/components/recorder/close-bg.svg') no-repeat;
  241. background-size: 100% 100%;
  242. }
  243. .close-btn {
  244. position: absolute;
  245. top: 12rpx;
  246. right: 10rpx;
  247. width: 20rpx;
  248. height: 20rpx;
  249. background: url('../../static/images/components/recorder/close.svg') no-repeat;
  250. background-size: 100% 100%;
  251. z-index: 999;
  252. }
  253. .play-icon {
  254. margin-right: 28rpx;
  255. width: 60rpx;
  256. height: 60rpx;
  257. }
  258. &:last-child {
  259. margin-bottom: 0;
  260. }
  261. .item-info {
  262. .top-name {
  263. font-family: Source Han Sans;
  264. font-size: 32rpx;
  265. color: $uni-text-color;
  266. }
  267. .bottom-content {
  268. margin-top: 10rpx;
  269. .base-info {
  270. display: flex;
  271. align-items: center;
  272. .icon {
  273. margin-right: 10rpx;
  274. width: 60rpx;
  275. height: 30rpx;
  276. }
  277. .duration {
  278. font-family: Source Han Sans;
  279. font-size: 28rpx;
  280. color: $uni-text-color-grey;
  281. }
  282. }
  283. }
  284. }
  285. }
  286. }
  287. .pop-up-box {
  288. .pop-up-content {
  289. border-top-left-radius: 20rpx;
  290. border-top-right-radius: 20rpx;
  291. .content-header {
  292. background-color: #fff;
  293. border-top-left-radius: 20rpx;
  294. border-top-right-radius: 20rpx;
  295. height: 100rpx;
  296. display: flex;
  297. align-items: center;
  298. justify-content: center;
  299. font-family: Source Han Sans;
  300. font-size: 32rpx;
  301. font-weight: 500;
  302. font-feature-settings: "kern" on;
  303. color: $uni-text-color;
  304. }
  305. .content-body {
  306. background: #EFF0F5;
  307. height: 400rpx;
  308. display: flex;
  309. flex-direction: column;
  310. align-items: center;
  311. justify-content: center;
  312. &-duration {
  313. font-family: Source Han Sans;
  314. font-size: 70rpx;
  315. color: $uni-text-color;
  316. }
  317. .icon {
  318. margin-top: 30rpx;
  319. width: 384rpx;
  320. height: 40rpx;
  321. }
  322. .btn {
  323. margin-top: 42rpx;
  324. }
  325. }
  326. }
  327. }
  328. }
  329. </style>