sound-recording.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <view class="recorder">
  3. <view class="re-top" v-if="showTop">
  4. <view class="re-cancel" @click="cancel">取消</view>
  5. <view class="re-confirm" :style="{color: theme}" @click="confirm">{{ confirmText }}</view>
  6. </view>
  7. <text class="title">{{ finish ? '点击播放' : '长按录制语音' }}</text>
  8. <view class="recorder-box"
  9. v-if="!finish"
  10. @click="handle"
  11. @longpress="onStartRecoder"
  12. @touchend="onEndRecoder">
  13. <u-circle-progress :active-color="theme" :duration="0" :percent="calcProgress">
  14. <view class="u-progress-content">
  15. <image src="/static/sound-recording/voice.png" mode="aspectFit" :style="{
  16. width: width,
  17. height: height
  18. }"></image>
  19. </view>
  20. </u-circle-progress>
  21. </view>
  22. <view class="recorder-box"
  23. v-else
  24. @click="playVoice">
  25. <u-circle-progress :active-color="theme" :duration="0" :percent="playProgress">
  26. <view class="u-progress-content">
  27. <image src="/static/sound-recording/play.png" mode="aspectFit" :style="{
  28. width: width,
  29. height: height
  30. }" v-if="!playStatus"></image>
  31. <image src="/static/sound-recording/pause.png" mode="aspectFit" :style="{
  32. width: width,
  33. height: height
  34. }" v-else></image>
  35. </view>
  36. </u-circle-progress>
  37. </view>
  38. <text class="now-date">{{ reDate }}</text>
  39. <view @click="reset">重新录制</view>
  40. </view>
  41. </template>
  42. <script>
  43. import uCircleProgress from '../u-circle-progress/u-circle-progress.vue'
  44. const recorderManager = uni.getRecorderManager();
  45. const innerAudioContext = uni.createInnerAudioContext();
  46. export default {
  47. components: {
  48. uCircleProgress
  49. },
  50. props: {
  51. width: {
  52. type: String,
  53. default: '60rpx'
  54. },
  55. height: {
  56. type: String,
  57. default: '60rpx'
  58. },
  59. showTop: {
  60. type: Boolean,
  61. default: true
  62. },
  63. maximum: {
  64. type: [Number, String],
  65. default: 15
  66. },
  67. duration: {
  68. type: Number,
  69. default: 20
  70. },
  71. theme: {
  72. type: String,
  73. default: '#32b99d'
  74. },
  75. confirmText: {
  76. type: String,
  77. default: '完成'
  78. }
  79. },
  80. data() {
  81. return {
  82. reDate: '00:00',
  83. sec: 0,
  84. min: 0,
  85. finish: false,
  86. voicePath: '',
  87. playProgress: 100,
  88. playTimer: null,
  89. timer: null,
  90. playStatus: false
  91. };
  92. },
  93. created () {
  94. // 监听
  95. this.onMonitorEvents()
  96. },
  97. computed: {
  98. // 录制时间计算
  99. calcProgress () {
  100. return (this.sec + (this.min * 60)) / this.maximum * 100
  101. }
  102. },
  103. methods: {
  104. // 完成事件
  105. confirm () {
  106. if (!innerAudioContext.paused) {
  107. innerAudioContext.stop()
  108. }
  109. this.$emit('confirm', this.voicePath)
  110. },
  111. // 取消事件
  112. cancel () {
  113. if (!innerAudioContext.paused) {
  114. innerAudioContext.stop()
  115. }
  116. this.$emit('cancel')
  117. },
  118. // 点击事件
  119. handle () {
  120. this.$emit('click')
  121. },
  122. // 重新录制
  123. reset () {
  124. this.voicePath = ''
  125. this.min = 0
  126. this.sec = 0
  127. this.reDate = '00:00'
  128. this.playProgress = 100
  129. this.finish = false
  130. this.$emit('reset')
  131. },
  132. // 播放暂停录音
  133. playVoice() {
  134. innerAudioContext.src = this.voicePath;
  135. if (innerAudioContext.paused) {
  136. innerAudioContext.play()
  137. this.playStatus = true
  138. } else {
  139. innerAudioContext.stop();
  140. }
  141. this.$emit('playVoice', innerAudioContext.paused)
  142. },
  143. // 录制结束
  144. onEndRecoder () {
  145. recorderManager.stop()
  146. },
  147. // 开始录制
  148. onStartRecoder () {
  149. recorderManager.start({
  150. duration: this.maximum * 1000
  151. })
  152. },
  153. // 监听
  154. onMonitorEvents () {
  155. // 录制开始
  156. recorderManager.onStart(() => {
  157. uni.showLoading({
  158. title: '录制中...'
  159. })
  160. this.startDate()
  161. this.$emit('start')
  162. })
  163. // 录制结束
  164. recorderManager.onStop(({ tempFilePath }) => {
  165. this.voicePath = tempFilePath
  166. clearInterval(this.timer)
  167. uni.hideLoading()
  168. this.finish = true
  169. this.$emit('end')
  170. })
  171. // 播放进度
  172. innerAudioContext.onTimeUpdate(() => {
  173. let totalDate = innerAudioContext.duration
  174. let nowTime = innerAudioContext.currentTime
  175. let surplus = totalDate - nowTime
  176. this.playProgress = surplus / totalDate * 100
  177. let _min = Math.floor(surplus / 60)
  178. if (_min < 10) _min = '0' + _min;
  179. let _sec = Math.floor(surplus%60)
  180. if (_sec < 10) _sec = '0' + _sec;
  181. this.reDate = _min + ':' + _sec
  182. })
  183. // 播放暂停
  184. innerAudioContext.onPause(() => {
  185. this.resetDate()
  186. this.playProgress = 100
  187. this.playStatus = false
  188. console.log('播放暂停')
  189. this.$emit('stop')
  190. })
  191. // 播放停止
  192. innerAudioContext.onStop(() => {
  193. this.resetDate()
  194. this.playProgress = 100
  195. this.playStatus = false
  196. console.log('播放停止')
  197. this.$emit('stop')
  198. })
  199. },
  200. // 录音计时
  201. startDate () {
  202. clearInterval(this.timer)
  203. this.sec = 0
  204. this.min = 0
  205. this.timer = setInterval(() => {
  206. this.sec += this.duration / 1000
  207. if (this.sec >= 60) {
  208. this.min ++
  209. this.sec = 0
  210. }
  211. this.resetDate()
  212. }, this.duration)
  213. },
  214. // 播放时间
  215. resetDate () {
  216. let _s = this.sec < 10 ? '0' + parseInt(this.sec) : parseInt(this.sec)
  217. let _m = this.min < 10 ? '0' + this.min : this.min
  218. this.reDate = _m + ':' + _s
  219. }
  220. }
  221. }
  222. </script>
  223. <style lang="scss">
  224. .recorder {
  225. position: relative;
  226. display: flex;
  227. align-items: center;
  228. flex-direction: column;
  229. background-color: #fff;
  230. font-size: 24rpx;
  231. width: 100%;
  232. .re-top {
  233. display: flex;
  234. justify-content: space-between;
  235. padding: 10rpx 20rpx;
  236. width: 100%;
  237. font-size: 28rpx;
  238. box-sizing: border-box;
  239. }
  240. .title {
  241. font-size: 36rpx;
  242. color: #333;
  243. padding: 20rpx 0 30rpx;
  244. }
  245. .recorder-box {
  246. position: relative;
  247. }
  248. .now-date {
  249. font-size: 28rpx;
  250. color: #666;
  251. padding: 20rpx 0;
  252. }
  253. }
  254. </style>