jc-record.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <template>
  2. <view class="jsfun-record" @tap="showPicker">
  3. <slot></slot>
  4. <!-- 遮罩层 -->
  5. <view class="mask" @tap.stop="closePicker" v-if="isShow" @touchmove.stop.prevent="moveHandle"></view>
  6. <!-- 多选控件 -->
  7. <view class="conbox record" :class="{'pickerShow':isShow}">
  8. <!-- 此处可放置倒计时,可根据需要自行添加 -->
  9. <view class="time">
  10. {{showRecordTime}}
  11. </view>
  12. <view class="c999">
  13. 最短{{minTime}}秒,最长{{maxTime}}秒
  14. </view>
  15. <view class="record-box" @touchstart="start" @longpress="record" @touchend="end"
  16. @touchmove.stop.prevent="moveHandle">
  17. <span class="stop" @touchstart.stop="stopVoice" v-if="voicePath && playing==1"></span>
  18. <span class="paly" @touchstart.stop="playVoice" v-if="voicePath && playing==0"></span>
  19. <canvas class="canvas" canvas-id="canvas">
  20. <span class="recording"></span>
  21. </canvas>
  22. <span class="confirm" @touchstart.stop="okClick" v-if="voicePath"></span>
  23. </view>
  24. <view class="c666 fz32 domess">长按录音</view>
  25. </view>
  26. </view>
  27. </template>
  28. <script>
  29. const recorderManager = uni.getRecorderManager(); //录音
  30. var innerAudioContext; //播放
  31. export default {
  32. name: 'jsfun-record',
  33. props: {
  34. maxTime: { // 录音最大时长,单位秒
  35. type: Number,
  36. default: 15
  37. },
  38. minTime: { // 录音最大时长,单位毫秒
  39. type: Number,
  40. default: 5
  41. },
  42. },
  43. data() {
  44. return {
  45. isShow: false,
  46. frame: 50, // 执行绘画的频率,单位毫秒
  47. recordTime: 0, //录音时长
  48. recordTime1: 0, //播放录音倒计时
  49. isshowyuan: false, //是否显示圆圈
  50. playing: 0, //是否播放中
  51. timeObj: null, //计时id
  52. drawObj: null, //画布动画id
  53. countdownObj: null, //倒计时id
  54. voicePath: '',
  55. ctx: ''
  56. }
  57. },
  58. computed: {
  59. showRecordTime() {
  60. var strs = "";
  61. var m = Math.floor(this.recordTime / 60);
  62. if (m < 10) strs = "0" + m;
  63. var s = this.recordTime % 60;
  64. strs += (s < 10) ? ":0" + s : ":" + s;
  65. return strs
  66. },
  67. },
  68. watch: {},
  69. onLoad() {
  70. var _this = this;
  71. //初始化
  72. _this.initValue();
  73. },
  74. mounted() {
  75. innerAudioContext = uni.createInnerAudioContext(); //播放
  76. let _this = this;
  77. //录音停止事件
  78. recorderManager.onStop(function(res) {
  79. console.log('recorder stop' + res.tempFilePath);
  80. _this.voicePath = res.tempFilePath;
  81. });
  82. //根据canvas 动态中心点
  83. let query = uni.createSelectorQuery().in(this);
  84. query.select(".canvas").boundingClientRect()
  85. query.exec(function(res) {
  86. _this.tempw = res[0].width; //使用canvas的宽度计算中心点的位置
  87. _this.temph = res[0].height;
  88. })
  89. //根据中心图片的大小计算圆环的大小
  90. query.select(".recording").boundingClientRect()
  91. query.exec(function(res) {
  92. _this.tempw1 = res[0].width; //使用点按图形的宽度计算圆环的宽高
  93. })
  94. },
  95. beforeDestroy() {
  96. innerAudioContext.destroy();
  97. },
  98. methods: {
  99. moveHandle() {
  100. return false;
  101. },
  102. //组件数据初始化 进入时、关闭时调用初始化
  103. initValue() {
  104. this.recordTime = 0
  105. },
  106. //显示组件
  107. showPicker() {
  108. setTimeout(() => {
  109. this.isShow = true;
  110. // this.$emit('show');
  111. }, 100);
  112. },
  113. //关闭组件
  114. closePicker() {
  115. this.isShow = false;
  116. //点遮罩 点取消关闭说明用户不想修改,所以这里对数据进行初始化
  117. this.initValue();
  118. this.stopVoice();
  119. },
  120. //点击确定
  121. okClick() {
  122. const hasPermission = await this.checkPermission(
  123. 'microphone', // 固定传值,双端通用
  124. '录制语音介绍需要麦克风权限,用于完善简历的个人展示内容' // 提示文案统一即可
  125. );
  126. if (!hasPermission) {
  127. uni.showToast({ title: '未获取麦克风权限,无法录制语音', icon: 'none' });
  128. return;
  129. }
  130. //获取录音权限
  131. uni.authorize({
  132. scope: 'scope.microphone',
  133. success() {}
  134. })
  135. // var data = {},list = {},textStr = "",indexStr = "";
  136. this.$emit('okClick', this.voicePath)
  137. this.$emit('start', 2)
  138. //确定后更新默认初始值,这样再次进入默认初值就是最后选择的
  139. // this.defaultArr = textStr;
  140. //关闭
  141. this.closePicker();
  142. },
  143. start() {
  144. console.log('start')
  145. this.stopVoice();
  146. this.voicePath = ""; //音频地址
  147. this.recordTime = 0;
  148. //生成canvas对象
  149. this.ctx = uni.createCanvasContext('canvas',this);
  150. },
  151. end() {
  152. console.log('end')
  153. let recordTime = this.recordTime;
  154. this.recordTime1 = this.recordTime;
  155. clearInterval(this.timeObj); //清除计时器
  156. clearInterval(this.drawObj); //清除画布动画
  157. // this.recordTime = 0; //清除计时
  158. this.isshowyuan = false; //隐藏圆圈
  159. //清除canvas内容 方式一:不知道为啥 不起作用
  160. //this.ctx.clearRect(0,0,this.ctx.width,this.ctx.height);
  161. //清除canvas内容 方式二:填充canvas为白色
  162. this.ctx.setFillStyle('#fff')
  163. this.ctx.fillRect(0, 0, this.ctx.width, this.ctx.height)
  164. this.ctx.draw()
  165. if (recordTime < this.minTime) {
  166. if (recordTime <= 0) {
  167. //==点击事件==;
  168. return false;
  169. }
  170. //==小于5秒==;
  171. uni.showToast({
  172. title: "不能小于" + this.minTime + "秒,请重新录制",
  173. icon: "none"
  174. })
  175. return false;
  176. }
  177. recorderManager.stop();
  178. },
  179. record: function() {
  180. console.log('record')
  181. let _this = this;
  182. _this.isshowyuan = true
  183. // 开始录音
  184. recorderManager.start({
  185. format: "mp3"
  186. });
  187. _this.timeObj = setInterval(function() {
  188. _this.recordTime++;
  189. if (_this.recordTime == _this.maxTime) _this.end();
  190. }, 1000);
  191. //中心点坐标 这里如果直接除2发现位置有偏差,目前还没明白为什么要减1
  192. let pianyi = 0
  193. switch (uni.getSystemInfoSync().platform) {
  194. case 'android':
  195. pianyi = 0;
  196. break;
  197. case 'ios':
  198. pianyi = 1;
  199. break;
  200. default:
  201. pianyi = 1;
  202. break;
  203. }
  204. // #ifdef APP-PLUS
  205. let centerX = _this.tempw / 2 + pianyi;
  206. let centerY = _this.temph / 2 + pianyi;
  207. // #endif
  208. // #ifdef MP-WEIXIN
  209. let centerX = _this.tempw / 2 + pianyi-1;
  210. let centerY = _this.temph / 2 + pianyi-1;
  211. // #endif
  212. let yuanhuanW = _this.tempw1 / 2 -6; //圆环的半径 中间图片的宽度/2 + 4
  213. // 录音过程圆圈动画的背景园
  214. _this.ctx.beginPath();
  215. _this.ctx.setStrokeStyle("#1789FD");
  216. _this.ctx.setGlobalAlpha(0.3)
  217. _this.ctx.setLineWidth(3);
  218. _this.ctx.arc(centerX, centerY, yuanhuanW, 0, 2 * Math.PI);
  219. _this.ctx.stroke();
  220. _this.ctx.draw();
  221. // 录音过程圆圈动画
  222. let angle = -0.5;
  223. _this.drawObj = setInterval(function() {
  224. _this.ctx.beginPath();
  225. _this.ctx.setStrokeStyle("#1789FD");
  226. _this.ctx.setGlobalAlpha(1)
  227. _this.ctx.setLineWidth(3);
  228. _this.ctx.arc(centerX, centerY, yuanhuanW, -0.5 * Math.PI, (angle += 2 / (_this
  229. .maxTime * 1000 / _this.frame)) * Math.PI, false);
  230. _this.ctx.stroke();
  231. _this.ctx.draw(true);
  232. }, _this.frame);
  233. },
  234. playVoice() {
  235. if (this.voicePath && this.playing === 0) {
  236. innerAudioContext.src = this.voicePath;
  237. innerAudioContext.stop(); //todo 第一次play时若不先stop则播放不出来,未知原因
  238. innerAudioContext.play();
  239. this.playing = 1;
  240. this.recordTime = this.recordTime1;
  241. this.countdownObj = setInterval(() => {
  242. this.recordTime--;
  243. if (this.recordTime === 0) {
  244. this.stopVoice()
  245. return;
  246. }
  247. }, 1000)
  248. }
  249. },
  250. stopVoice() {
  251. innerAudioContext.stop();
  252. this.playing = 0;
  253. this.recordTime = 0;
  254. clearInterval(this.countdownObj);
  255. },
  256. }
  257. }
  258. </script>
  259. <style lang="scss">
  260. .jsfun-record {
  261. .mask {
  262. position: fixed;
  263. z-index: 1000;
  264. top: 0;
  265. right: 0;
  266. left: 0;
  267. bottom: 0;
  268. background: rgba(0, 0, 0, 0.6);
  269. }
  270. .conbox {
  271. transition: all .3s ease;
  272. transform: translateY(100%);
  273. &.pickerShow {
  274. transform: translateY(0);
  275. }
  276. position: fixed;
  277. z-index: 1000;
  278. right: 0;
  279. left: 0;
  280. bottom: 0;
  281. background: #fff;
  282. }
  283. .c666 {
  284. color: #666;
  285. }
  286. .c999 {
  287. color: #999;
  288. }
  289. .fz28 {
  290. font-size: 28upx;
  291. }
  292. .fz32 {
  293. font-size: 32upx;
  294. }
  295. .record {
  296. text-align: center;
  297. .time {
  298. text-align: center;
  299. font-size: 60upx;
  300. color: #000;
  301. line-height: 100upx;
  302. margin-top: 50upx;
  303. }
  304. .domess {
  305. margin-bottom: 50upx;
  306. }
  307. .record-box {
  308. display: flex;
  309. flex-direction: row;
  310. justify-content: center;
  311. }
  312. canvas {
  313. margin: 10upx 60upx;
  314. position: relative;
  315. width: 200upx;
  316. height: 200upx;
  317. z-index: 10;
  318. .recording {
  319. position: absolute;
  320. top: 20upx;
  321. left: 20upx;
  322. width: 160upx;
  323. height: 160upx;
  324. border: 1px dashed #1789FD;
  325. border-radius: 100upx;
  326. background: #1789FD url(../../static/jsfun-record/recording.png) no-repeat 50% 50%;
  327. background-size: 50% 50%;
  328. z-index: 100;
  329. }
  330. }
  331. .btncom {
  332. margin-top: 70upx;
  333. width: 80upx;
  334. height: 80upx;
  335. border-radius: 80upx;
  336. }
  337. .stop {
  338. @extend .btncom;
  339. background: #1789FD url(../../static/jsfun-record/stop.png) no-repeat;
  340. background-size: 100% 100%;
  341. }
  342. .paly {
  343. @extend .btncom;
  344. background: #1789FD url(../../static/jsfun-record/play.png) no-repeat;
  345. background-size: 100% 100%;
  346. }
  347. .confirm {
  348. @extend .btncom;
  349. background: url(../../static/jsfun-record/confirm.png) no-repeat 100% 100%;
  350. background-size: 100% 100%;
  351. }
  352. }
  353. }
  354. </style>