audio.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  2. <view class="TUI-message-input-main" @longpress="handleLongPress" @touchmove="handleTouchMove"
  3. @touchend="handleTouchEnd">
  4. <text>{{ text }}</text>
  5. <view class="record-modal" v-if="popupToggle" @longpress="handleLongPress" @touchmove="handleTouchMove"
  6. @touchend="handleTouchEnd">
  7. <view class="wrapper">
  8. <view class="modal-loading"></view>
  9. </view>
  10. <view class="modal-title">{{ title }}</view>
  11. </view>
  12. </view>
  13. </template>
  14. <script lang="ts">
  15. import {
  16. defineComponent,
  17. watchEffect,
  18. reactive,
  19. toRefs,
  20. onMounted
  21. } from 'vue';
  22. // #ifndef H5
  23. const recorderManager = uni.getRecorderManager();
  24. // #endif
  25. const AudioMessage = defineComponent({
  26. props: {
  27. show: {
  28. type: Boolean,
  29. default: () => {
  30. return false;
  31. }
  32. },
  33. },
  34. setup(props: any, ctx: any) {
  35. const data = reactive({
  36. popupToggle: false,
  37. isRecording: false,
  38. canSend: true,
  39. text: '按住说话',
  40. recorderManager: null,
  41. title: ' ',
  42. recordTime: 0,
  43. recordTimer: null,
  44. });
  45. onMounted(() => {
  46. // 加载声音录制管理器
  47. recorderManager.onStop(res => {
  48. clearInterval(data.recordTimer);
  49. // 兼容 uniapp 打包app,duration 和 fileSize 需要用户自己补充
  50. // 文件大小 = (音频码率) x 时间长度(单位:秒) / 8
  51. let msg = {
  52. duration: res.duration ? res.duration : data.recordTime * 1000,
  53. tempFilePath: res.tempFilePath,
  54. fileSize: res.fileSize ? res.fileSize : ((48 * data.recordTime) / 8) *
  55. 1024
  56. };
  57. uni.hideLoading();
  58. // 兼容 uniapp 语音消息没有duration
  59. if (data.canSend) {
  60. if (msg.duration < 1000) {
  61. uni.showToast({
  62. title: '录音时间太短',
  63. icon: 'none'
  64. });
  65. } else {
  66. // res.tempFilePath 存储录音文件的临时路径
  67. uni.$TUIKit.TUIChatServer.sendAudioMessage(msg)
  68. }
  69. }
  70. data.popupToggle = false;
  71. data.isRecording = false;
  72. data.canSend = true;
  73. data.title = ' ';
  74. data.text = '按住说话'
  75. });
  76. });
  77. const handleLongPress = (e: any) => {
  78. data.popupToggle = true,
  79. recorderManager.start({
  80. duration: 60000,
  81. // 录音的时长,单位 ms,最大值 600000(10 分钟)
  82. sampleRate: 44100,
  83. // 采样率
  84. numberOfChannels: 1,
  85. // 录音通道数
  86. encodeBitRate: 192000,
  87. // 编码码率
  88. format: 'aac' // 音频格式,选择此格式创建的音频消息,可以在即时通信 IM 全平台(Android、iOS、微信小程序和Web)互通
  89. });
  90. data.startPoint = e.target,
  91. data.title = '正在录音',
  92. data.isRecording = true,
  93. data.recordTime = 0
  94. data.recordTimer = setInterval(() => {
  95. data.recordTime++;
  96. }, 1000);
  97. };
  98. // 录音时的手势上划移动距离对应文案变化
  99. const handleTouchMove = (e: any) => {
  100. if (data.isRecording) {
  101. if (e.currentTarget.offsetTop - e.changedTouches[e.changedTouches.length - 1].clientY >
  102. 100) {
  103. data.text = '抬起停止';
  104. data.title = '松开手指,取消发送';
  105. data.canSend = false;
  106. } else if (e.currentTarget.offsetTop - e.changedTouches[e.changedTouches.length - 1]
  107. .clientY > 20) {
  108. data.text = '抬起停止';
  109. data.title = '上划可取消';
  110. data.canSend = true;
  111. } else {
  112. data.text = '抬起停止';
  113. data.title = '正在录音';
  114. data.canSend = true;
  115. }
  116. }
  117. };
  118. // 手指离开页面滑动
  119. const handleTouchEnd = () => {
  120. data.isRecording = false;
  121. data.popupToggle = false;
  122. data.text = '按住说话';
  123. uni.hideLoading();
  124. recorderManager.stop();
  125. };
  126. // 发送需要上传的消息:视频
  127. const sendUploadMessage = (e: any) => {
  128. Video.TUIServer.sendVideoMessage(e.target);
  129. };
  130. return {
  131. ...toRefs(data),
  132. sendUploadMessage,
  133. handleLongPress,
  134. handleTouchEnd,
  135. handleTouchMove
  136. };
  137. },
  138. });
  139. export default AudioMessage;
  140. </script>
  141. <style lang="scss" scoped>
  142. .audio-contain {
  143. display: flex;
  144. justify-content: center;
  145. font-size: 32rpx;
  146. font-family: PingFangSC-Regular;
  147. }
  148. .TUI-message-input-main {
  149. background-color: #fff;
  150. flex: 1;
  151. height: 66rpx;
  152. margin: 0 10rpx;
  153. padding: 0 5rpx;
  154. border-radius: 5rpx;
  155. display: flex;
  156. align-items: center;
  157. }
  158. .record-modal {
  159. height: 300rpx;
  160. width: 60vw;
  161. background-color: #000;
  162. opacity: 0.8;
  163. position: fixed;
  164. top: 670rpx;
  165. z-index: 9999;
  166. left: 20vw;
  167. border-radius: 24rpx;
  168. display: flex;
  169. flex-direction: column;
  170. }
  171. .record-modal .wrapper {
  172. display: flex;
  173. height: 200rpx;
  174. box-sizing: border-box;
  175. padding: 10vw;
  176. }
  177. .record-modal .wrapper .modal-loading {
  178. opacity: 1;
  179. width: 40rpx;
  180. height: 16rpx;
  181. border-radius: 4rpx;
  182. background-color: #006fff;
  183. animation: loading 2s cubic-bezier(0.17, 0.37, 0.43, 0.67) infinite;
  184. }
  185. @keyframes loading {
  186. 0% {
  187. transform: translate(0, 0)
  188. }
  189. 50% {
  190. transform: translate(30vw, 0);
  191. background-color: #f5634a;
  192. width: 40px;
  193. }
  194. 100% {
  195. transform: translate(0, 0);
  196. }
  197. }
  198. .modal-title {
  199. text-align: center;
  200. color: #fff;
  201. }
  202. </style>