jc-record.vue 9.1 KB

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