h5-record.vue
9.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
<!--
* @Date: 2024-02-01 11:11:21
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-02-01 15:03:08
* @FilePath: /my-record/src/views/h5-record.vue
* @Description: 文件描述
-->
<template>
<div class="h5-record">
<div class="wave-wrapper">
<div class="ctrlProcessWave wave-view"></div>
<div class="wave-info">
<div class="ctrlProcessX wave-info-top" :style="{ width: powerLevel + '%' }" ></div>
<div class="ctrlProcessT wave-info-bottom" >
{{ durationTxt + "/" + powerLevel }}
</div>
</div>
</div>
<div style="margin: 20px 10px;">
<audio ref="LogAudioPlayer" style="width: 100%"></audio>
</div>
<van-floating-panel>
<van-cell-group>
<van-cell title="打开录音,请求权限" @click.native="recOpen" />
<van-cell title="关闭录音,释放资源" @click.native="recClose" />
<van-cell title="录制" @click.native="recStart" />
<van-cell title="暂停" @click.native="recPause" />
<van-cell title="继续录音" @click.native="recResume" />
<van-cell title="停止录音" @click.native="recStop" />
<van-cell title="播放录音" @click.native="recPlayLast" />
<van-cell title="下载录音" @click.native="recDownLast" />
<van-cell title="上传录音" @click.native="recUploadLast" />
</van-cell-group>
</van-floating-panel>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
import { v4 as uuidv4 } from 'uuid';
import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from '@/api/common'
//加载必须要的core,demo简化起见采用的直接加载类库,实际使用时应当采用异步按需加载
import Recorder from "recorder-core";
//需要使用到的音频格式编码引擎的js文件统统加载进来,这些引擎文件会比较大
import "recorder-core/src/engine/mp3";
import "recorder-core/src/engine/mp3-engine";
//可选的扩展
import "recorder-core/src/extensions/waveview";
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const type = ref("mp3"); // 音频类型
const bitRate = ref(32); // 比特率
const sampleRate = ref(32000); // 采样率
const duration = ref(0);
const durationTxt = ref("0");
const powerLevel = ref(0);
const logs = ref([]);
const recLogLast = ref({});
const LogAudioPlayer = ref(null);
const rec = ref(null); // 录音控件实例
const wave = ref(null); // 波形绘制对象
const formatMs = (ms, all) => { // 格式化时间显示
var ss = ms % 1000;
ms = (ms - ss) / 1000;
var s = ms % 60;
ms = (ms - s) / 60;
var m = ms % 60;
ms = (ms - m) / 60;
var h = ms;
var t =
(h ? h + ":" : "") +
(all || h + m ? ("0" + m).substr(-2) + ":" : "") +
(all || h + m + s ? ("0" + s).substr(-2) + "″" : "") +
("00" + ss).substr(-3);
return t;
}
/**
* 录音日志
* @param {String} msg 日志内容
* @param {String} color 日志颜色
* @param {Object} res 日志资源
*/
const reclog = (msg, color, res) => {
var obj = {
idx: logs.value.length,
msg: msg,
color: color,
res: res,
playMsg: "",
down: 0,
down64Val: "",
};
if (res && res.blob) {
recLogLast.value = obj;
}
logs.value.splice(0, 0, obj);
}
/**
* 打开录音
*/
const recOpen = () => {
rec.value = Recorder({
type: type.value,
bitRate: +bitRate.value,
sampleRate: +sampleRate.value,
onProcess: function (buffers, p, d, sampleRate) { // 播放进程中
duration.value = d;
durationTxt.value = formatMs(d, 1);
powerLevel.value = p;
// 改变波纹显示
wave.value.input(buffers[buffers.length - 1], p, sampleRate);
},
});
rec.value.open(
() => { // 成功回调
reclog(
"已打开:" + type.value + " " + sampleRate.value + "hz " + bitRate.value + "kbps",
2
);
wave.value = Recorder.WaveView({ elem: ".ctrlProcessWave" });
},
(msg, isUserNotAllow) => { // 失败回调
reclog((isUserNotAllow ? "UserNotAllow," : "") + "打开失败:" + msg, 1);
}
);
}
/**
* 关闭录音
*/
const recClose = () => {
if (rec.value) {
rec.value.close();
reclog("已关闭");
} else {
reclog("未打开录音", 1);
}
}
/**
* 开始录音
*/
const recStart = () => {
if (!rec.value || !Recorder.IsOpen()) {
reclog("未打开录音", 1);
return;
}
rec.value.start();
let set = rec.value.set;
reclog(
"录制中:" + set.type + " " + set.sampleRate + "hz " + set.bitRate + "kbps"
);
}
/**
* 暂停录音
*/
const recPause = () => {
if (rec.value && Recorder.IsOpen()) {
rec.value.pause();
} else {
reclog("未打开录音", 1);
}
}
/**
* 继续录音
*/
const recResume = () => {
if (rec.value && Recorder.IsOpen()) {
rec.value.resume();
} else {
reclog("未打开录音", 1);
}
}
/**
* 停止录音
*/
const recStop = () => {
if (!(rec.value && Recorder.IsOpen())) {
reclog("未打开录音", 1);
return;
}
rec.value.stop(
function (blob, duration) {
reclog("已录制:", "", {
blob: blob,
duration: duration,
durationTxt: formatMs(duration),
rec: rec,
});
},
function (s) {
reclog("录音失败:" + s, 1);
}
);
}
const recPlay = (idx) => {
let o = logs.value[logs.value.length - idx - 1];
o.play = (o.play || 0) + 1;
let audio = LogAudioPlayer.value;
audio.controls = true;
if (!(audio.ended || audio.paused)) {
audio.pause();
}
audio.onerror = function (e) {
console.warn("播放失败:" + e);
};
audio.src = (window.URL || webkitURL).createObjectURL(o.res.blob);
audio.play();
}
/**
* 播放录音
*/
const recPlayLast = () => {
if (!recLogLast.value) {
reclog("请先录音,然后停止后再播放", 1);
return;
}
recPlay(recLogLast.value.idx);
}
const recDown = (idx) => {
let o = logs.value[logs.value.length - idx - 1];
o.down = (o.down || 0) + 1;
o = o.res;
let name =
"rec-" +
o.duration +
"ms-" +
(o.rec.set.bitRate || "-") +
"kbps-" +
(o.rec.set.sampleRate || "-") +
"hz." +
(o.rec.set.type || (/\w+$/.exec(o.blob.type) || [])[0] || "unknown");
var downA = document.createElement("A");
downA.href = (window.URL || webkitURL).createObjectURL(o.blob);
downA.download = name;
downA.click();
}
/**
* 下载录音
*/
const recDownLast = () => {
if (!recLogLast.value) {
reclog("请先录音,然后停止后再下载", 1);
return;
}
recDown(recLogLast.value.idx);
}
/**
* 转换格式base64
*/
const recDown64 = (idx) => {
let o = logs.value[logs.value.length - idx - 1];
let reader = new FileReader();
reader.onloadend = function () {
o.down64Val = reader.result;
};
reader.readAsDataURL(o.res.blob);
}
/**
* 上传录音
*/
const recUploadLast = () => {
if (!recLogLast.value) {
reclog("请先录音,然后停止后再上传", 1);
return;
}
let blob = recLogLast.value.res.blob;
let reader = new FileReader();
reader.onloadend = async function () {
let base64url = encodeURIComponent((/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1]); //录音文件内容,后端进行base64解码成二进制
let affix = uuidv4();
// TAG: 获取token和保存的接口应该有问题
// 获取七牛token
const { token, key, code } = await qiniuTokenAPI({ filename: `${affix}_my_record.mp3`, file: base64url });
if (code) {
let formData = new FormData();
formData.append("file", base64url); // 通过append向form对象添加数据
formData.append("token", token);
formData.append("key", `${affix}_my_record.mp3`);
let config = {
headers: { "Content-Type": "multipart/form-data" },
};
// 自拍图片上传七牛服务器
let qiniuUploadUrl;
if (window.location.protocol === 'https:') {
qiniuUploadUrl = 'https://up.qbox.me';
} else {
qiniuUploadUrl = 'http://upload.qiniu.com';
}
const { filekey, hash } = await qiniuUploadAPI(
qiniuUploadUrl,
formData,
config
);
if (filekey) {
const { data } = await saveFileAPI({ filekey, hash });
// console.warn(filekey)
}
}
};
reader.readAsDataURL(blob);
}
</script>
<style lang="less" scoped>
.h5-record {
background-color: #f9f9f9;
height: 100vh;
.wave-wrapper {
.wave-view {
height: 100px;
width: 100vw;
border: 1px solid #ccc;
box-sizing: border-box;
display: inline-block;
vertical-align: bottom;
}
.wave-info {
height: 40px;
width: 100vw;
display: inline-block;
background: #999;
position: relative;
vertical-align: bottom;
.wave-info-top {
position: absolute;
height: 40px;
background: #0b1;
}
.wave-info-bottom {
position: relative;
padding-left: 50px;
line-height: 40px;
}
}
}
}
</style>