index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <template>
  2. <view>
  3. <view class="audio_start_box" @click="startRecordAudio">
  4. <image src="/static/images/new_ui/inputbar/audio_click_start_icon.png">
  5. </image>
  6. <text>点击录音</text>
  7. </view>
  8. <u-popup :show="isStartRecording" @close="isStartRecording = false">
  9. <view class="audio_record_box">
  10. <image
  11. class="audio_icon"
  12. @click="cannelRecordAudio"
  13. src="/static/images/new_ui/inputbar/audio_delete_icon.png"
  14. ></image>
  15. <view class="record_statistics_box">
  16. <view class="record_length_text">{{ recordTime }}s</view>
  17. <text>正在录音</text>
  18. </view>
  19. <image
  20. class="audio_icon"
  21. @click="sendAudioMessage"
  22. src="/static/images/new_ui/inputbar/audio_send_icon.png"
  23. ></image>
  24. </view>
  25. </u-popup>
  26. </view>
  27. </template>
  28. <script>
  29. import { EMClient } from '@/EaseIM';
  30. import { emMessages } from '@/EaseIM/emApis';
  31. import { RECORD_STATUS, RECOED_DESC } from '@/constant';
  32. const { sendDisplayMessages } = emMessages();
  33. let recordTimeInterval = null;
  34. export default {
  35. data() {
  36. return {
  37. RECOED_DESC,
  38. RECORD_STATUS,
  39. isStartRecording: false,
  40. recordStatus: RECORD_STATUS.HIDE,
  41. recorderManager: null, //全局唯一录音管理器
  42. recording: false, //是否正在录音中
  43. recordTime: 0,
  44. webRecorderManager: null, // h5 audio record
  45. platFormInfo: uni.getSystemInfoSync(),
  46. };
  47. },
  48. mounted() {
  49. this.platFormInfo = uni.getSystemInfoSync();
  50. if (this.platFormInfo !== 'web') {
  51. this.recorderManager = uni.getRecorderManager();
  52. }
  53. },
  54. watch: {
  55. recordTime(newVal, oldVal) {
  56. if (this.recording && newVal > 50) {
  57. uni.showToast({
  58. icon: 'none',
  59. title: '录音时长最大60秒,注意时长!',
  60. duration: 2000,
  61. });
  62. this.cannelRecordAudio();
  63. }
  64. if (this.recording && newVal > 60) {
  65. uni.showToast({
  66. icon: 'none',
  67. title: '录音时长最大60秒',
  68. duration: 2000,
  69. });
  70. this.cannelRecordAudio();
  71. }
  72. },
  73. },
  74. computed: {
  75. chattingId() {
  76. return this.$store.getters.chattingId;
  77. },
  78. chattingChatType() {
  79. return this.$store.getters.chattingChatType;
  80. },
  81. },
  82. methods: {
  83. startRecordAudio() {
  84. console.log('>>>>>开始采集');
  85. this.isStartRecording = true;
  86. this.handleRecording();
  87. },
  88. handleRecording() {
  89. // h5不支持uni.getRecorderManager, 需要单独处理
  90. if (this.platFormInfo.uniPlatform === 'web') {
  91. console.log('>>>>>>>>++++++');
  92. this.executeWebAudioRecord();
  93. } else {
  94. setTimeout(() => {
  95. //请求授权
  96. this.requestMiniProgramAuthorization();
  97. }, 350);
  98. }
  99. },
  100. //web平台采集音频录音
  101. //#ifdef WEB
  102. executeWebAudioRecord() {
  103. // import('../../../../recorderCore/src/recorder-core').then((Recorder) => {
  104. // require('../../../../recorderCore/src/engine/mp3');
  105. // require('../../../../recorderCore/src/engine/mp3-engine');
  106. // this.initStartRecord();
  107. // this.webRecorderManager = new Recorder.default({
  108. // type: 'mp3',
  109. // });
  110. // this.webRecorderManager.open(
  111. // () => {
  112. // console.log('>>>>>>web 录音开始执行');
  113. // this.recording = true;
  114. // this.saveRecordTime();
  115. // this.webRecorderManager.start();
  116. // },
  117. // (msg, isUserNotAllow) => {
  118. // if (isUserNotAllow) {
  119. // uni.showToast({
  120. // title: '鉴权失败,请重试',
  121. // icon: 'none',
  122. // });
  123. // } else {
  124. // uni.showToast({
  125. // title: `开启失败,请重试`,
  126. // icon: 'none',
  127. // });
  128. // }
  129. // }
  130. // );
  131. // });
  132. },
  133. // #endif
  134. requestMiniProgramAuthorization() {
  135. const onSuccess = (res) => {
  136. this.initStartRecord();
  137. const recordAuth = res.authSetting['scope.record'];
  138. if (!recordAuth) {
  139. // 已申请过授权,但是用户拒绝
  140. uni.openSetting({
  141. success: function (res) {
  142. const recordAuth = res.authSetting['scope.record'];
  143. if (recordAuth == true) {
  144. uni.showToast({
  145. title: '授权成功',
  146. icon: 'success',
  147. });
  148. } else {
  149. uni.showToast({
  150. title: '请授权录音',
  151. icon: 'none',
  152. });
  153. }
  154. },
  155. });
  156. return;
  157. }
  158. // 用户已经同意授权
  159. if (recordAuth) {
  160. this.executeMiniProgramRecord();
  161. return;
  162. }
  163. // 第一次进来,未发起授权
  164. uni.authorize({
  165. scope: 'scope.record',
  166. success: () => {
  167. // 授权成功
  168. uni.showToast({
  169. title: '授权成功',
  170. icon: 'icon',
  171. });
  172. this.executeMiniProgramRecord();
  173. },
  174. });
  175. };
  176. const onFail = (res) => {
  177. uni.showToast({
  178. title: '鉴权失败,请重试',
  179. icon: 'none',
  180. });
  181. };
  182. if (uni.getSetting) {
  183. uni.getSetting({
  184. success: onSuccess,
  185. fail: onFail,
  186. });
  187. return;
  188. } else {
  189. this.executeMiniProgramRecord();
  190. return;
  191. }
  192. },
  193. //针对mini program 平台采集音频录音
  194. executeMiniProgramRecord() {
  195. this.initStartRecord();
  196. let recorderManager = this.recorderManager || uni.getRecorderManager();
  197. recorderManager.onStart(() => {
  198. this.recording = true;
  199. this.saveRecordTime();
  200. });
  201. recorderManager.start({
  202. format: 'mp3',
  203. });
  204. },
  205. // 初始化开始录音状态
  206. initStartRecord() {
  207. clearInterval(recordTimeInterval);
  208. this.recordTime = 0;
  209. this.recording = false;
  210. },
  211. getRecorderCollectionData() {
  212. const recorderManager = this.recorderManager;
  213. const webRecorderManager = this.webRecorderManager;
  214. const duration = this.recordTime * 1000;
  215. return new Promise((resolve, reject) => {
  216. if (webRecorderManager && this.platFormInfo.uniPlatform === 'web') {
  217. this.webRecorderManager.stop(
  218. (blob) => {
  219. clearInterval(recordTimeInterval);
  220. if (duration <= 1000) {
  221. uni.showToast({
  222. title: '录音时间太短',
  223. icon: 'none',
  224. });
  225. } else {
  226. const tempFilePath = window.URL.createObjectURL(blob);
  227. resolve({ tempFilePath, duration });
  228. }
  229. this.recordTime = 0;
  230. },
  231. (s) => {
  232. reject(s);
  233. console.log('结束出错:' + s, 1);
  234. },
  235. true
  236. );
  237. } else if (recorderManager) {
  238. recorderManager.onStop((res) => {
  239. if (duration <= 1000) {
  240. uni.showToast({
  241. title: '录音时间太短',
  242. icon: 'none',
  243. });
  244. } else {
  245. // 上传
  246. resolve({ tempFilePath: res.tempFilePath, duration });
  247. }
  248. this.initStartRecord();
  249. });
  250. // 停止录音
  251. recorderManager.stop();
  252. }
  253. });
  254. },
  255. //取消录音
  256. cannelRecordAudio() {
  257. //调用对应管理器结束录音采集
  258. if (this.platFormInfo.uniPlatform === 'web' && this.webRecorderManager) {
  259. this.webRecorderManager.close(() => {
  260. this.initStartRecord();
  261. });
  262. } else if (this.recorderManager) {
  263. this.recorderManager.onStop(() => {
  264. this.initStartRecord();
  265. });
  266. this.recorderManager.stop();
  267. }
  268. this.isStartRecording = false;
  269. this.$emit('changeRecordAudioContainer');
  270. },
  271. //上传录音附件资源至环信服务器
  272. async uploadRecordFile(tempFilePath) {
  273. if (!tempFilePath) return;
  274. const apiUrl = EMClient.apiUrl;
  275. const orgName = EMClient.orgName;
  276. const appName = EMClient.appName;
  277. const uploadTargetUrl = `${apiUrl}/${orgName}/${appName}/chatfiles`;
  278. const accessToken = EMClient.token;
  279. return new Promise((resolve, reject) => {
  280. const requestParams = {
  281. url: uploadTargetUrl,
  282. filePath: tempFilePath,
  283. fileType: 'audio',
  284. name: 'file',
  285. header: {
  286. Authorization: 'Bearer ' + accessToken,
  287. },
  288. success: (res) => {
  289. console.log('>>>>>录音上传成功', res);
  290. uni.showToast({ title: '音源已上传...', icon: 'none' });
  291. resolve(res);
  292. },
  293. fail: (e) => {
  294. console.log('>>>>>上传失败', e);
  295. uni.showToast({ title: '录音上传失败', icon: 'none' });
  296. reject(e);
  297. },
  298. };
  299. uni.uploadFile(requestParams);
  300. });
  301. },
  302. //发送语音消息
  303. async sendAudioMessage() {
  304. try {
  305. //获取对应音频源文件
  306. const { tempFilePath, duration } =
  307. await this.getRecorderCollectionData();
  308. //进行文件上传
  309. const uploadFileRes = await this.uploadRecordFile(tempFilePath);
  310. const dataObj = JSON.parse(uploadFileRes.data); // 接收消息对象
  311. const bodys = {
  312. url: dataObj.uri + '/' + dataObj.entities[0].uuid,
  313. filename: `${new Date().getTime()}.mp3`,
  314. filetype: 'mp3',
  315. length: Math.ceil(duration / 1000),
  316. };
  317. const params = {
  318. // 消息类型。
  319. type: 'audio',
  320. body: { ...bodys },
  321. filename: bodys.filename,
  322. // 消息接收方:单聊为对方用户 ID,群聊和聊天室分别为群组 ID 和聊天室 ID。
  323. to: this.chattingId,
  324. // 会话类型:单聊、群聊和聊天室分别为 `singleChat`、`groupChat` 和 `chatRoom`。
  325. chatType: this.chattingChatType,
  326. ext: {},
  327. };
  328. const res = await sendDisplayMessages({ ...params });
  329. console.log('>>>>>>已发送', res);
  330. } catch (error) {
  331. console.log('>>>>>语音消息发送失败', error);
  332. uni.showToast({
  333. title: `消息发送失败${error}`,
  334. icon: 'none',
  335. });
  336. } finally {
  337. this.isStartRecording = false;
  338. this.$emit('changeRecordAudioContainer');
  339. }
  340. },
  341. // 记录录音时长
  342. saveRecordTime() {
  343. recordTimeInterval = setInterval(() => {
  344. this.recordTime++;
  345. }, 1000);
  346. },
  347. },
  348. beforeDestroy() {
  349. clearInterval(recordTimeInterval);
  350. this.recordTime = 0;
  351. },
  352. };
  353. </script>
  354. <style scoped>
  355. .audio_record_box {
  356. width: 100%;
  357. height: 200px;
  358. display: flex;
  359. flex-direction: row;
  360. align-items: center;
  361. justify-content: space-around;
  362. background: #fff;
  363. }
  364. .audio_start_box {
  365. width: 100%;
  366. height: 200px;
  367. display: flex;
  368. flex-direction: column;
  369. align-items: center;
  370. justify-content: center;
  371. }
  372. .audio_start_box image {
  373. width: 48px;
  374. height: 48px;
  375. }
  376. .audio_start_box text {
  377. /* 点击录音 */
  378. width: 56px;
  379. height: 18px;
  380. /* 简体中文/标签/中 */
  381. font-family: 'PingFang SC';
  382. font-style: normal;
  383. font-weight: 500;
  384. font-size: 14px;
  385. line-height: 18px;
  386. /* identical to box height, or 129% */
  387. text-align: center;
  388. /* Neutral/5 */
  389. color: #75828a;
  390. /* Inside auto layout */
  391. flex: none;
  392. order: 1;
  393. flex-grow: 0;
  394. }
  395. .audio_icon {
  396. width: 36px;
  397. height: 36px;
  398. }
  399. .record_statistics_box {
  400. display: flex;
  401. flex-direction: column;
  402. align-items: center;
  403. width: 80px;
  404. height: 82px;
  405. }
  406. .record_length_text {
  407. width: 80px;
  408. height: 48px;
  409. line-height: 48px;
  410. text-align: center;
  411. background: #009eff;
  412. border-radius: 45px;
  413. border: 10px solid #e5f5ff;
  414. color: #e5f5ff;
  415. }
  416. .record_statistics_box text {
  417. /* 正在录音 */
  418. width: 56px;
  419. height: 18px;
  420. /* 简体中文/标签/中 */
  421. font-family: 'PingFang SC';
  422. font-style: normal;
  423. font-weight: 500;
  424. font-size: 14px;
  425. line-height: 18px;
  426. /* identical to box height, or 129% */
  427. text-align: center;
  428. /* Neutral/5 */
  429. color: #75828a;
  430. /* Inside auto layout */
  431. flex: none;
  432. order: 1;
  433. flex-grow: 0;
  434. }
  435. </style>