Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
my-record
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
hookehuyr
2024-02-01 18:51:46 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
603c67d01ddc2ef30d6aee3659cecf5312a6e7d8
603c67d0
1 parent
035f2190
录音模块功能
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
394 additions
and
20 deletions
components.d.ts
package.json
src/api/fn.js
src/main.js
src/route.js
src/utils/axios.js
src/views/h5-record.vue
src/views/record.vue
yarn.lock
components.d.ts
View file @
603c67d
...
...
@@ -9,5 +9,11 @@ declare module 'vue' {
export
interface
GlobalComponents
{
RouterLink
:
typeof
import
(
'vue-router'
)[
'RouterLink'
]
RouterView
:
typeof
import
(
'vue-router'
)[
'RouterView'
]
VanButton
:
typeof
import
(
'vant/es'
)[
'Button'
]
VanCell
:
typeof
import
(
'vant/es'
)[
'Cell'
]
VanCellGroup
:
typeof
import
(
'vant/es'
)[
'CellGroup'
]
VanCol
:
typeof
import
(
'vant/es'
)[
'Col'
]
VanFloatingPanel
:
typeof
import
(
'vant/es'
)[
'FloatingPanel'
]
VanRow
:
typeof
import
(
'vant/es'
)[
'Row'
]
}
}
...
...
package.json
View file @
603c67d
...
...
@@ -27,7 +27,7 @@
"recorder-core"
:
"^1.3.23122400"
,
"typescript"
:
"^4.7.3"
,
"uuid"
:
"^8.3.2"
,
"vant"
:
"^4.8.
1
"
,
"vant"
:
"^4.8.
3
"
,
"vconsole"
:
"^3.14.6"
,
"vite-plugin-dynamic-import"
:
"^0.9.6"
,
"vite-plugin-mp"
:
"^1.6.1"
,
...
...
src/api/fn.js
View file @
603c67d
/*
* @Date: 2022-05-18 22:56:08
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-0
1-02 16:46:57
* @FilePath: /
tswj
/src/api/fn.js
* @LastEditTime: 2024-0
2-01 14:42:39
* @FilePath: /
my-record
/src/api/fn.js
* @Description: 文件描述
*/
import
axios
from
'@/utils/axios'
;
...
...
@@ -49,7 +49,7 @@ export const fn = (api) => {
export
const
uploadFn
=
(
api
)
=>
{
return
api
.
then
(
res
=>
{
if
(
res
.
statusText
===
'OK'
)
{
if
(
res
.
statusText
===
'OK'
||
res
.
status
===
200
)
{
return
res
.
data
||
true
;
}
else
{
// tslint:disable-next-line: no-console
...
...
src/main.js
View file @
603c67d
...
...
@@ -2,12 +2,12 @@
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-31 12:06:19
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 202
3-12-31 17:32:10
* @FilePath: /
tswj
/src/main.js
* @LastEditTime: 202
4-02-01 11:59:18
* @FilePath: /
my-record
/src/main.js
* @Description:
*/
import
{
createApp
}
from
'vue'
;
import
{
Button
,
Image
as
VanImage
,
Col
,
Row
,
Icon
,
Form
,
Field
,
CellGroup
,
ConfigProvider
,
Toast
,
Uploader
,
Empty
,
Tab
,
Tabs
,
Overlay
,
NumberKeyboard
,
Lazyload
,
List
,
PullRefresh
,
Popup
,
Picker
,
Sticky
,
Stepper
,
Tag
,
Swipe
,
SwipeItem
,
Dialog
,
ActionSheet
,
Loading
,
Checkbox
,
Search
,
Notify
}
from
'vant'
;
import
{
Button
,
Image
as
VanImage
,
Col
,
Row
,
Icon
,
Form
,
Field
,
CellGroup
,
ConfigProvider
,
Toast
,
Uploader
,
Empty
,
Tab
,
Tabs
,
Overlay
,
NumberKeyboard
,
Lazyload
,
List
,
PullRefresh
,
Popup
,
Picker
,
Sticky
,
Stepper
,
Tag
,
Swipe
,
SwipeItem
,
Dialog
,
ActionSheet
,
Loading
,
Checkbox
,
Search
,
Notify
,
FloatingPanel
}
from
'vant'
;
import
router
from
'./router'
;
import
App
from
'./App.vue'
;
// import axios from './utils/axios';
...
...
@@ -21,6 +21,6 @@ const app = createApp(App);
app
.
config
.
globalProperties
.
$http
=
axios
;
// 关键语句
app
.
use
(
pinia
).
use
(
router
).
use
(
Button
).
use
(
VanImage
).
use
(
Col
).
use
(
Row
).
use
(
Icon
).
use
(
Form
).
use
(
Field
).
use
(
CellGroup
).
use
(
Toast
).
use
(
Uploader
).
use
(
Empty
).
use
(
Tab
).
use
(
Tabs
).
use
(
Overlay
).
use
(
NumberKeyboard
).
use
(
Lazyload
).
use
(
List
).
use
(
PullRefresh
).
use
(
Popup
).
use
(
Picker
).
use
(
Sticky
).
use
(
Stepper
).
use
(
Tag
).
use
(
Swipe
).
use
(
SwipeItem
).
use
(
Dialog
).
use
(
ActionSheet
).
use
(
Loading
).
use
(
Checkbox
).
use
(
Search
).
use
(
ConfigProvider
).
use
(
Notify
);
app
.
use
(
pinia
).
use
(
router
).
use
(
Button
).
use
(
VanImage
).
use
(
Col
).
use
(
Row
).
use
(
Icon
).
use
(
Form
).
use
(
Field
).
use
(
CellGroup
).
use
(
Toast
).
use
(
Uploader
).
use
(
Empty
).
use
(
Tab
).
use
(
Tabs
).
use
(
Overlay
).
use
(
NumberKeyboard
).
use
(
Lazyload
).
use
(
List
).
use
(
PullRefresh
).
use
(
Popup
).
use
(
Picker
).
use
(
Sticky
).
use
(
Stepper
).
use
(
Tag
).
use
(
Swipe
).
use
(
SwipeItem
).
use
(
Dialog
).
use
(
ActionSheet
).
use
(
Loading
).
use
(
Checkbox
).
use
(
Search
).
use
(
ConfigProvider
).
use
(
Notify
)
.
use
(
FloatingPanel
)
;
app
.
mount
(
'#app'
);
...
...
src/route.js
View file @
603c67d
...
...
@@ -10,4 +10,10 @@ export default [{
meta
:
{
title
:
'record'
}
},
{
path
:
'/h5-record'
,
component
:
()
=>
import
(
'@/views/h5-record.vue'
),
meta
:
{
title
:
'h5-record'
}
}];
...
...
src/utils/axios.js
View file @
603c67d
...
...
@@ -2,8 +2,8 @@
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-28 10:17:40
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-0
1-02 18:02:3
1
* @FilePath: /
tswj
/src/utils/axios.js
* @LastEditTime: 2024-0
2-01 14:34:5
1
* @FilePath: /
my-record
/src/utils/axios.js
* @Description:
*/
import
axios
from
'axios'
;
...
...
src/views/h5-record.vue
0 → 100644
View file @
603c67d
<!--
* @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>
src/views/record.vue
View file @
603c67d
<!--
* @Date: 2024-02-01 09:45:51
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-02-01
09:47:13
* @LastEditTime: 2024-02-01
18:29:41
* @FilePath: /my-record/src/views/record.vue
* @Description: 文件描述
-->
...
...
@@ -140,7 +140,8 @@ a:hover {
<div class="mainBox">
<!-- 放一个 <audio ></audio> 播放器,标签名字大写,阻止uniapp里面乱编译 -->
<AUDIO ref="LogAudioPlayer" style="width: 100%"></AUDIO>
<!-- <AUDIO ref="LogAudioPlayer" style="width: 100%"></AUDIO> -->
<audio ref="LogAudioPlayer" style="width: 100%"></audio>
<div class="mainLog">
<div v-for="obj in logs" :key="obj.idx">
...
...
@@ -202,9 +203,9 @@ export default {
logs: [],
};
},
created: function () {
this.Rec = Recorder;
},
//
created: function () {
//
this.Rec = Recorder;
//
},
methods: {
recOpen: function () {
var This = this;
...
...
@@ -293,7 +294,6 @@ export default {
}
);
},
recPlayLast: function () {
if (!this.recLogLast) {
this.reclog("请先录音,然后停止后再播放", 1);
...
...
yarn.lock
View file @
603c67d
...
...
@@ -2916,10 +2916,10 @@ uuid@^8.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
vant@^4.8.
1
:
version "4.8.
1
"
resolved "https://mirrors.cloud.tencent.com/npm/vant/-/vant-4.8.
1.tgz
"
integrity sha512-
SkFZM3Z3Bwi5do+iQNfRgDi7b+Ka29rUUNzck06W2KoFie3CLTqSifLa5TuZCEoXPSkqR+fRH/VE5G57mmL8sg
==
vant@^4.8.
3
:
version "4.8.
3
"
resolved "https://mirrors.cloud.tencent.com/npm/vant/-/vant-4.8.
3.tgz#f9a4d57c331edffc2174dfd26c1d0e6e1cdb6cae
"
integrity sha512-
swJdNeeBKOx80O7z3NiQuVhYROnagjCT6zgOMK9Tsbki9AIf3pOZVk8605AuM7CFX4ZS/CndhKoIFlavob6c4A
==
dependencies:
"@vant/popperjs" "^1.3.0"
"@vant/use" "^1.6.0"
...
...
Please
register
or
login
to post a comment