项目初始化

This commit is contained in:
jerry
2025-01-21 01:46:34 +08:00
parent 364021b042
commit 48153e7761
962 changed files with 172070 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
## 1.1.12024-05-22
1.新增node后端方便测试
2.修复H5及H5上传示例
## 1.0.72024-05-22
更新H5上传示例新增node后端测试 自行进行测试
## 1.0.62024-03-19
上传示例项目
## 1.0.52024-03-19
修复PC语音录制
## 1.0.42024-03-19
兼容pc
## 1.0.02024-03-19
兼容PC
## 1.0.32024-03-15
修复安卓导入失败
## 1.0.22024-03-14
修复小程序报错支持vue3
## 1.0.12024-03-05
补充说明,原作者信息。本人只是补充
## 1.0.02024-03-05
### 首次发布
1.原地址 https://ext.dcloud.net.cn/plugin?id=9595
2.原有基础上兼容H5,如有侵权联系立刻下架

View File

@@ -0,0 +1,652 @@
<template>
<view>
<view class="cbb-record">
<view class="conbox record">
<!-- 此处可放置倒计时可根据需要自行添加 -->
<view class="time">
{{showRecordTime}}
</view>
<view class="c999">
最短{{minTime}}最长{{maxTime}}
</view>
<view class="record-box">
<view class="stop" @tap.stop="stopVoice" v-if="_voicePath && playing==1 && recordTime1 > minTime-1">
<u-icon color="#3cc9a4" name="pause-circle" size="80"></u-icon>
</view>
<view class="paly" @tap.stop="playVoice" v-if="_voicePath && playing==0 && recordTime1 > minTime-1">
<u-icon color="#3cc9a4" name="play-circle" size="80"></u-icon>
</view>
<!-- #ifdef APP-PLUS ||MP-WEIXIN -->
<view class="circle-box"
@longpress="startRecord"
@touchend="endRecord">
<view class="voice-btn">
<view class="progress-icon">
<u-icon name="mic" color="#fff" size="70"></u-icon>
</view>
</view>
<!-- <u-circle-progress width="170" active-color="#2979ff" :percent="percent" :duration="duration">
<view class="u-progress-content">
<view class="u-progress-dot">
<u-icon name="mic" size="60"></u-icon>
</view>
</view>
</u-circle-progress> -->
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="circle-box" @click="startRecord">
<view class="voice-btn">
<view class="progress-icon">
<u-icon name="mic" color="#fff" size="70"></u-icon>
</view>
</view>
<!-- <u-circle-progress width="170" active-color="#2979ff" :percent="percent" :duration="duration">
<view class="u-progress-content">
<view class="u-progress-dot">
<u-icon name="mic" size="60"></u-icon>
</view>
</view>
</u-circle-progress> -->
</view>
<!-- #endif -->
<view class="confirm" @tap.stop="okClick" v-if="_voicePath && recordTime1 > minTime-1">
<u-icon color="#3cc9a4" name="checkmark-circle" size="80"></u-icon>
</view>
</view>
<view class="c666 fz32 domess">{{ btnTextContent }}</view>
</view>
</view>
</view>
</template>
<script>
var that;
var innerAudioContext;//播放
// #ifdef APP-PLUS || MP-WEIXIN
const recorderManager = uni.getRecorderManager();
// #endif
// #ifdef H5
import speech from '../../js_sdk/h5-speech/speech.js';
const recorderManager = new speech(8000);
// #endif
// #ifdef APP-PLUS
// 引入权限判断
import permision from '../../js_sdk/wa-permission/permission.js';
// #endif
import tuiCircularProgress from "@/components/thorui/tui-circular-progress/tui-circular-progress.vue"
export default {
name: 'nbVoiceRecord',
components:{
tuiCircularProgress,
},
/**
* 录音交互动效组件
* @property {Object} recordOptions 录音配置
* @property {Object} btnStyle 按钮样式
* @property {Object} btnHoverFontcolor 按钮长按时字体颜色
* @property {String} btnHoverBgcolor 按钮长按时背景颜色
* @property {String} btnDefaultText 按钮初始文字
* @property {String} btnRecordingText 录制时按钮文字
* @property {Boolean} vibrate 弹窗时是否震动
* @property {String} popupTitle 弹窗顶部文字
* @property {String} popupDefaultTips 录制时弹窗底部提示
* @property {String} popupCancelTips 滑动取消时弹窗底部提示
* @property {String} popupMaxWidth 弹窗展开后宽度
* @property {String} popupMaxHeight 弹窗展开后高度
* @property {String} popupFixBottom 弹窗展开后距底部高度
* @property {String} popupBgColor 弹窗背景颜色
* @property {String} lineHeight 声波高度
* @property {String} lineStartColor 声波波谷时颜色色值
* @property {String} lineEndColor 声波波峰时颜色色值
* @event {Function} startRecord 开始录音回调
* @event {Function} endRecord 结束录音回调
* @event {Function} cancelRecord 滑动取消录音回调
* @event {Function} stopRecord 主动停止录音
*/
props: {
recordOptions: {
type: Object,
default() {
return {
duration: 600000
}; // 请自行查看各端的的支持情况,这里全部使用默认配置
}
},
btnStyle: {
type: Object,
default() {
return {
width: '300rpx',
height: '80rpx',
borderRadius: '20rpx',
backgroundColor: '#fff',
border: '1rpx solid whitesmoke',
permisionState: false
};
}
},
btnHoverFontcolor: {
type: String,
default: '#000' // 颜色名称或16进制色值
},
btnHoverBgcolor: {
type: String,
default: 'whitesmoke' // 颜色名称或16进制色值
},
btnDefaultText: {
type: String,
default: '长按开始录音'
},
btnRecordingText: {
type: String,
default: '录音中'
},
vibrate: {
type: Boolean,
default: true
},
// #ifdef APP-PLUS || MP-WEIXIN
popupTitle: {
type: String,
default: '正在录制音频'
},
popupDefaultTips: {
type: String,
default: '左右滑动后松手完成录音'
},
// #endif
// #ifdef H5
popupTitle: {
type: String,
default: '点击录制音频'
},
popupDefaultTips: {
type: String,
default: '点击完成录音'
},
// #endif
popupCancelTips: {
type: String,
default: '松手取消录音'
},
popupMaxWidth: {
type: Number,
default: 600 // 单位为rpx
},
popupMaxHeight: {
type: Number,
default: 300 // 单位为rpx
},
popupFixBottom: {
type: Number,
default: 200 // 单位为rpx
},
popupBgColor: {
type: String,
default: 'whitesmoke'
},
lineHeight: {
type: Number,
default: 50 // 单位为rpx
},
lineStartColor: {
type: String,
default: 'royalblue' // 颜色名称或16进制色值
},
lineEndColor: {
type: String,
default: 'indianred' // 颜色名称或16进制色值
},
voicePath: { //默认地址
type: String,
default: ''
},
maxTime: { // 录音最大时长,单位秒
type: Number,
default: 15
},
minTime: { // 录音最小时长,单位毫秒
type:Number ,
default: 5
},
},
data() {
return {
stopStatus: true, // 是否已被父页面通知主动结束录音
btnTextContent: this.btnDefaultText,
startTouchData: {},
popupHeight: '0px', // 这是初始的高度
recording: true, // 录音中
recordPopupShow: false,
recordTimeout: null, // 录音定时器
h5start: false,
isShow:false,
playing:0,//是否播放中
timeObj: null, //计时id
countdownObj: null, //倒计时id
recordTime: 0,//录音时长
recordTime1:0,//播放录音倒计时
percent: 0,
duration1: true,
newViocePath: '',
};
},
created() {
that = this;
innerAudioContext = uni.createInnerAudioContext();//播放
// 请求权限
this.checkPermission();
// #ifdef APP-PLUS || MP-WEIXIN
recorderManager.onStop((res) => {
that.newViocePath = res.tempFilePath;
that.endRecord();
that.$emit('endRecord', res);
});
recorderManager.onStart((err) => {
console.log('开始:', err);
});
recorderManager.onError((err) => {
console.log('err:', err);
});
// #endif
},
computed: {
showRecordTime() {
var strs = "";
var m = Math.floor(this.recordTime/60);
if(m<10) strs = "0"+m;
var s = this.recordTime%60;
strs += (s<10) ? ":0"+s : ":"+s;
return strs
},
duration() {
return this.duration1 ? this.maxTime*1000 : 0;
},
_voicePath(){
return this.newViocePath || this.voicePath;
}
},
methods: {
upx2px(upx) {
return uni.upx2px(upx) + 'px';
},
async checkPermission() {
var that = this;
// #ifdef APP-PLUS
// 先判断os
let os = uni.getSystemInfoSync().osName;
if (os == 'ios') {
this.permisionState = await permision.judgeIosPermission('record');
} else {
this.permisionState = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
}
if (this.permisionState !== true && this.permisionState !== 1) {
uni.showToast({
title: '请先授权使用录音',
icon: 'none'
});
return;
}
// #endif
// #ifdef H5
if (!window.navigator?.mediaDevices?.getUserMedia) {
this.permisionState = false;
uni.showToast({
title: '请先授权使用录音',
icon: 'none'
});
return;
} else {
this.permisionState = true;
}
// #endif
// #ifdef MP-WEIXIN
uni.authorize({
scope: 'scope.record',
success(e) {
that.permisionState = true;
// that.startRecord();
},
fail() {
uni.showToast({
title: '请授权使用录音',
icon: 'none'
});
}
});
// #endif
},
startRecord() {
this.percent = 0;
this.duration1 = true;
if (!this.permisionState) {
this.checkPermission();
return;
}
if (this.h5start) {
this.duration1 = false;
this.h5start = false;
this.endRecord();
return;
}
this.h5start = true;
this.stopStatus = false;
this.stopVoice();
this.percent = 100;
this.recordTime = 0;
this.newViocePath = "";//音频地址
this.btnTextContent = this.btnRecordingText;
this.timeObj = setInterval(() => {
this.recordTime ++;
if(this.recordTime == this.maxTime) {
this.endRecord();
}
},1000);
setTimeout(() => {
setTimeout(() => {
if (this.vibrate) {
// #ifdef APP-PLUS
// 震动
plus.device.vibrate(35);
// #endif
// #ifdef MP-WEIXIN
uni.vibrateShort();
// #endif
}
// 开始录音
recorderManager.start(this.recordOptions);
this.$emit('startRecord');
}, 100);
}, 200);
},
endRecord() {
this.percent = 0;
this.h5start = false;
let recordTime = this.recordTime;
this.recordTime1 = this.recordTime;
clearInterval(this.timeObj); //清除计时器
var that = this;
if (this.stopStatus) {
return;
}
//this.popupHeight = '0px';
//this.recordPopupShow = false;
this.btnTextContent = this.btnDefaultText;
// #ifdef APP-PLUS || MP-WEIXIN
recorderManager.stop();
// #endif
// #ifdef H5
const res = recorderManager.stop();
that.newViocePath = res;
that.$emit('endRecord', res);
// #endif
},
stopRecord() {
// 用法如你录音限制了时间,那么将在结束时强制停止组件的显示
this.endRecord();
this.stopStatus = true;
},
touchStart(e) {
this.startTouchData.clientX = e.changedTouches[0].clientX; //手指按下时的X坐标
this.startTouchData.clientY = e.changedTouches[0].clientY; //手指按下时的Y坐标
},
touchMove(e) {
let touchData = e.touches[0]; //滑动过程中,手指滑动的坐标信息 返回的是Objcet对象
let moveX = touchData.clientX - this.startTouchData.clientX;
let moveY = touchData.clientY - this.startTouchData.clientY;
if (moveY < -50) {
if (this.vibrate && this.recording) {
// #ifdef APP-PLUS
plus.device.vibrate(35);
// #endif
// #ifdef MP-WEIXIN
uni.vibrateShort();
// #endif
}
this.recording = false;
} else {
this.recording = true;
}
},
playVoice() {
if (this._voicePath && this.playing === 0) {
console.log('playVoice', this._voicePath)
innerAudioContext.src = this._voicePath;
innerAudioContext.stop(); //todo 第一次play时若不先stop则播放不出来,未知原因
innerAudioContext.play();
this.playing = 1;
this.recordTime = this.recordTime1;
this.countdownObj = setInterval(() => {
this.recordTime--;
if(this.recordTime === 0){
this.recordTime = this.recordTime1;
this.stopVoice()
return;
}
}, 1000)
}
},
startVoice() {
this.isShow = true;
this.percent = 0;
this.recordTime = 0;
this.newViocePath = "";//音频地址
},
//关闭组件
closePicker(){
this.isShow = false;
this.endRecord();
this.stopVoice();
},
stopVoice() {
innerAudioContext.stop();
this.playing = 0;
//this.recordTime = 0;
clearInterval(this.countdownObj);
},
//点击确定
okClick(){
var data = {
path: this._voicePath,
sec: this.recordTime1,
}
this.stopVoice();
this.$emit('okClick', data);
},
}
};
</script>
<style lang="scss">
.cbb-record {
.conbox{
background: #fff;
}
.record{
text-align: center;
.time {
text-align: center;
font-size: 60upx;
color: #000;
line-height: 100upx;
//margin-top:50upx;
}
.domess{margin-bottom:50upx;}
.c666{color:#aaa;}
.c999{color:#999;}
.fz28{font-size: 28upx;}
.fz32{font-size: 32upx;}
.record-box {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 10px 0;
}
.btncom{
width: 80upx;
height: 80upx;
border-radius: 80upx;
}
.stop{
@extend .btncom;
}
.paly{
@extend .btncom;
}
.confirm{
@extend .btncom;
}
}
.circle-box {
margin: 0 80rpx;
}
}
.record-popup {
position: absolute;
bottom: var(--popup-bottom);
left: calc(50vw - calc(var(--popup-width) / 2));
z-index: 1;
width: var(--popup-width);
height: var(--popup-height);
display: flex;
align-items: center;
justify-content: center;
border-radius: 10rpx;
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
background: var(--popup-bg-color);
color: #000;
transition: 0.2s height;
.inner-content {
height: var(--popup-height);
font-size: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.title {
font-weight: bold;
padding: 20rpx 0;
}
.tips {
color: #999;
padding: 20rpx 0;
}
}
}
.voice-btn {
width: 170rpx;
height: 170rpx;
}
.progress-icon {
display: flex;
justify-content: center;
align-items: center;
background-color: #3cc9a4;
border-radius: 100%;
width: 100%;
height: 100%;
}
.cancel-icon {
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 44rpx;
line-height: 44rpx;
background-color: pink;
border-radius: 50%;
transform: rotate(45deg);
}
.voice-line-wrap {
display: flex;
align-items: center;
.voice-line {
width: 5rpx;
height: var(--line-height);
border-radius: 3rpx;
margin: 0 5rpx;
}
.one {
animation: wave 0.4s 1s linear infinite alternate;
}
.two {
animation: wave 0.4s 0.9s linear infinite alternate;
}
.three {
animation: wave 0.4s 0.8s linear infinite alternate;
}
.four {
animation: wave 0.4s 0.7s linear infinite alternate;
}
.five {
animation: wave 0.4s 0.6s linear infinite alternate;
}
.six {
animation: wave 0.4s 0.5s linear infinite alternate;
}
.seven {
animation: wave 0.4s linear infinite alternate;
}
}
@keyframes wave {
0% {
transform: scale(1, 1);
background-color: var(--line-start-color);
}
100% {
transform: scale(1, 0.2);
background-color: var(--line-end-color);
}
}
</style>

View File

@@ -0,0 +1,226 @@
class Recoder {
constructor(sampleRate) {
this.leftDataList = []
this.rightDataList = []
this.mediaPlayer = null
this.audioContext = null
this.source = null
this.sampleRate = sampleRate || 44100
}
start() {
return new Promise((resolve, reject) => {
window.navigator.mediaDevices.getUserMedia({
audio: {
sampleRate: 8000, // 采样率
channelCount: 1, // 声道
audioBitsPerSecond: 64,
volume: 1.0, // 音量
autoGainControl: true
}
}).then(mediaStream => {
console.log(mediaStream, 'mediaStream')
this.mediaPlayer = mediaStream
this.beginRecord(mediaStream)
resolve()
}).catch(err => {
// 如果用户电脑没有麦克风设备或者用户拒绝了,或者连接出问题了等
// 这里都会抛异常并且通过err.name可以知道是哪种类型的错误
console.error(err)
reject(err)
})
})
}
beginRecord(mediaStream) {
let audioContext = new(window.AudioContext || window.webkitAudioContext)()
// mediaNode包含 mediaStreamaudioContext
let mediaNode = audioContext.createMediaStreamSource(mediaStream)
console.log(mediaNode, 'mediaNode')
// 创建一个jsNode
// audioContext.sampleRate = 8000
console.log(audioContext, 'audioContext')
let jsNode = this.createJSNode(audioContext)
console.log(jsNode, 'jsnode')
// 需要连到扬声器消费掉outputBufferprocess回调才能触发
// 并且由于不给outputBuffer设置内容所以扬声器不会播放出声音
jsNode.connect(audioContext.destination)
jsNode.onaudioprocess = this.onAudioProcess.bind(this)
// 把mediaNode连接到jsNode
mediaNode.connect(jsNode)
this.audioContext = audioContext
}
onAudioProcess(event) {
console.log('is recording')
// 拿到输入buffer Float32Array
let audioBuffer = event.inputBuffer
let leftChannelData = audioBuffer.getChannelData(0)
// let rightChannelData = audioBuffer.getChannelData(1)
// 需要克隆一下
this.leftDataList.push(leftChannelData.slice(0))
//this.rightDataList.push(rightChannelData.slice(0))
}
createJSNode(audioContext) {
const BUFFER_SIZE = 4096
const INPUT_CHANNEL_COUNT = 1
const OUTPUT_CHANNEL_COUNT = 1
// createJavaScriptNode已被废弃
let creator = audioContext.createScriptProcessor || audioContext.createJavaScriptNode
creator = creator.bind(audioContext)
return creator(BUFFER_SIZE, INPUT_CHANNEL_COUNT, OUTPUT_CHANNEL_COUNT)
}
playRecord(arrayBuffer) {
let blob = new Blob([new Int8Array(arrayBuffer)], {
type: 'audio/mp3' // files[0].type
})
let blobUrl = URL.createObjectURL(blob)
this.source = blob
this.blobUrl = blobUrl
// document.querySelector(.audio-node).src = blobUrl
return blobUrl
}
stop() {
// 停止录音
let leftData = this.mergeArray(this.leftDataList)
//let rightData = this.mergeArray(this.rightDataList)
let allData = this.interSingleData(leftData)
let wavBuffer = this.createWavFile(allData)
// 转到播放
let source = this.playRecord(wavBuffer)
// if (source) {
// source = source.slice(5)
// }
console.log("我最后转换完播放的---------------------", source);
this.resetRecord()
return source
}
transformArrayBufferToBase64(buffer) {
var binary = ''
var bytes = new Uint8Array(buffer)
for (var len = bytes.byteLength, i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
return window.btoa(binary)
}
// 停止控件录音
resetRecord() {
this.leftDataList = []
this.rightDataList = []
this.audioContext.close()
this.mediaPlayer.getAudioTracks().forEach(track => {
track.stop()
this.mediaPlayer.removeTrack(track)
})
}
createWavFile(audioData) {
let channelCount = 1
const WAV_HEAD_SIZE = 44
const sampleBits = 16
let sampleRate = this.sampleRate
let buffer = new ArrayBuffer(audioData.length * 2 + WAV_HEAD_SIZE)
// 需要用一个view来操控buffer
let view = new DataView(buffer)
// 写入wav头部信息
// RIFF chunk descriptor/identifier
this.writeUTFBytes(view, 0, 'RIFF')
// RIFF chunk length
view.setUint32(4, 44 + audioData.length * channelCount, true)
// RIFF type
this.writeUTFBytes(view, 8, 'WAVE')
// format chunk identifier
// FMT sub-chunk
this.writeUTFBytes(view, 12, 'fmt ')
// format chunk length
view.setUint32(16, 16, true)
// sample format (raw)
view.setUint16(20, 1, true)
// stereo (2 channels)
view.setUint16(22, channelCount, true)
// sample rate
view.setUint32(24, sampleRate, true)
// byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 2, true)
// block align (channel count * bytes per sample)
view.setUint16(32, 2 * 2, true)
// bits per sample
view.setUint16(34, 16, true)
// data sub-chunk
// data chunk identifier
this.writeUTFBytes(view, 36, 'data')
// data chunk length
view.setUint32(40, audioData.length * 2, true)
console.log(view, 'view')
let length = audioData.length
let index = 44
let volume = 1
for (let i = 0; i < length; i++) {
view.setInt16(index, audioData[i] * (0x7FFF * volume), true)
index += 2
}
return buffer
}
writeUTFBytes(view, offset, string) {
var lng = string.length
for (var i = 0; i < lng; i++) {
view.setUint8(offset + i, string.charCodeAt(i))
}
}
interSingleData(left) {
var t = left.length;
let sampleRate = this.audioContext.sampleRate,
outputSampleRate = this.sampleRate
sampleRate += 0.0;
outputSampleRate += 0.0;
var s = 0,
o = sampleRate / outputSampleRate,
u = Math.ceil(t * outputSampleRate / sampleRate),
a = new Float32Array(u);
for (let i = 0; i < u; i++) {
a[i] = left[Math.floor(s)];
s += o;
}
return a;
}
// 交叉合并左右声道的数据
interleaveLeftAndRight(left, right) {
let totalLength = left.length + right.length
let data = new Float32Array(totalLength)
for (let i = 0; i < left.length; i++) {
let k = i * 2
data[k] = left[i]
data[k + 1] = right[i]
}
return data
}
mergeArray(list) {
let length = list.length * list[0].length
let data = new Float32Array(length)
let offset = 0
for (let i = 0; i < list.length; i++) {
data.set(list[i], offset)
offset += list[i].length
}
return data
}
}
export default Recoder

View File

@@ -0,0 +1,272 @@
/**
* 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
*/
var isIos
// #ifdef APP-PLUS
isIos = (plus.os.name == "iOS")
// #endif
// 判断推送权限是否开启
function judgeIosPermissionPush() {
var result = false;
var UIApplication = plus.ios.import("UIApplication");
var app = UIApplication.sharedApplication();
var enabledTypes = 0;
if (app.currentUserNotificationSettings) {
var settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
console.log("enabledTypes1:" + enabledTypes);
if (enabledTypes == 0) {
console.log("推送权限没有开启");
} else {
result = true;
console.log("已经开启推送功能!")
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
console.log("推送权限没有开启!");
} else {
result = true;
console.log("已经开启推送功能!")
}
console.log("enabledTypes2:" + enabledTypes);
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
return result;
}
// 判断定位权限是否开启
function judgeIosPermissionLocation() {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var status = cllocationManger.authorizationStatus();
result = (status != 2)
console.log("定位权限开启:" + result);
// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
/* var enable = cllocationManger.locationServicesEnabled();
var status = cllocationManger.authorizationStatus();
console.log("enable:" + enable);
console.log("status:" + status);
if (enable && status != 2) {
result = true;
console.log("手机定位服务已开启且已授予定位权限");
} else {
console.log("手机系统的定位没有打开或未给予定位权限");
} */
plus.ios.deleteObject(cllocationManger);
return result;
}
// 判断麦克风权限是否开启
function judgeIosPermissionRecord() {
var result = false;
var avaudiosession = plus.ios.import("AVAudioSession");
var avaudio = avaudiosession.sharedInstance();
var permissionStatus = avaudio.recordPermission();
console.log("permissionStatus:" + permissionStatus);
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
console.log("麦克风权限没有开启");
} else {
result = true;
console.log("麦克风权限已经开启");
}
plus.ios.deleteObject(avaudiosession);
return result;
}
// 判断相机权限是否开启
function judgeIosPermissionCamera() {
var result = false;
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = true;
console.log("相机权限已经开启");
} else {
console.log("相机权限没有开启");
}
plus.ios.deleteObject(AVCaptureDevice);
return result;
}
// 判断相册权限是否开启
function judgeIosPermissionPhotoLibrary() {
var result = false;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = true;
console.log("相册权限已经开启");
} else {
console.log("相册权限没有开启");
}
plus.ios.deleteObject(PHPhotoLibrary);
return result;
}
// 判断通讯录权限是否开启
function judgeIosPermissionContact() {
var result = false;
var CNContactStore = plus.ios.import("CNContactStore");
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus == 3) {
result = true;
console.log("通讯录权限已经开启");
} else {
console.log("通讯录权限没有开启");
}
plus.ios.deleteObject(CNContactStore);
return result;
}
// 判断日历权限是否开启
function judgeIosPermissionCalendar() {
var result = false;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
if (ekAuthStatus == 3) {
result = true;
console.log("日历权限已经开启");
} else {
console.log("日历权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
// 判断备忘录权限是否开启
function judgeIosPermissionMemo() {
var result = false;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
if (ekAuthStatus == 3) {
result = true;
console.log("备忘录权限已经开启");
} else {
console.log("备忘录权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
// Android权限查询
function requestAndroidPermission(permissionID) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
function(resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
// if (result != 1) {
// gotoAppPermissionSetting()
// }
},
function(error) {
console.log('申请权限错误:' + error.code + " = " + error.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
}
// 使用一个方法,根据参数判断权限
function judgeIosPermission(permissionID) {
if (permissionID == "location") {
return judgeIosPermissionLocation()
} else if (permissionID == "camera") {
return judgeIosPermissionCamera()
} else if (permissionID == "photoLibrary") {
return judgeIosPermissionPhotoLibrary()
} else if (permissionID == "record") {
return judgeIosPermissionRecord()
} else if (permissionID == "push") {
return judgeIosPermissionPush()
} else if (permissionID == "contact") {
return judgeIosPermissionContact()
} else if (permissionID == "calendar") {
return judgeIosPermissionCalendar()
} else if (permissionID == "memo") {
return judgeIosPermissionMemo()
}
return false;
}
// 跳转到**应用**的权限页面
function gotoAppPermissionSetting() {
if (isIos) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
// 检查系统的设备服务是否开启
// var checkSystemEnableLocation = async function () {
function checkSystemEnableLocation() {
if (isIos) {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var result = cllocationManger.locationServicesEnabled();
console.log("系统定位开启:" + result);
plus.ios.deleteObject(cllocationManger);
return result;
} else {
var context = plus.android.importClass("android.content.Context");
var locationManager = plus.android.importClass("android.location.LocationManager");
var main = plus.android.runtimeMainActivity();
var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
console.log("系统定位开启:" + result);
return result
}
}
export default {
judgeIosPermission: judgeIosPermission,
requestAndroidPermission: requestAndroidPermission,
checkSystemEnableLocation: checkSystemEnableLocation,
gotoAppPermissionSetting: gotoAppPermissionSetting
}

View File

@@ -0,0 +1,82 @@
{
"id": "all-speech",
"displayName": "allspeech长按录音动画组件多端权限判断可监听开始、结束、取消事件",
"version": "1.1.1",
"description": "https://ext.dcloud.net.cn/plugin?id=9595这个是原项目本人再次基础上兼容了H5",
"keywords": [
"长按,录音,动画组件"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://gitee.com/imboya/nb-voice-record"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "y",
"IE": "n",
"Edge": "n",
"Firefox": "y",
"Safari": "n"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,89 @@
### nbVoiceRecord概述
- 这是个基于uni-app 符合uni_modules 的插件
- 无任何依赖、纯css动画
- nb是NeverBug的意思
### 主要功能
- 长按组件后弹出录音弹窗,松手完成录音,手指向上滑动可取消;
- 支持各种自定义,如弹窗高度、宽度、各处文字甚至声纹波形的尺寸和颜色;
- 已完成多端适配,自动根据授权情况提示完成授权、已获得授权才开始录音
- endRecord回调事件附带录音文件
### 动画预览
- 默认样式
![默认样式](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-613ff9e2-568b-4845-987f-93626e21bcde/84cf3c4f-f4f2-41e6-bb82-1414465a944d.gif)
- 自定义按钮为圆形(红背景、白字)、弹窗为正方形
![正方形](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-613ff9e2-568b-4845-987f-93626e21bcde/893bf1d6-593f-40e6-aeff-12afe4ebbc37.gif)
### 基本用法:
```
<template>
<view>
<all-speech @startRecord="start" @endRecord="end" @cancelRecord="cancel"></all-speech>
</view>
</template>
<script>
methods: {
start() {
// 开始录音
},
end(event) {
// 结束录音并处理得到的录音文件
// event中app端仅有tempFilePath字段微信小程序还有duration和fileSize两个字段
},
cancel() {
// 用户取消录音
}
}
</script>
```
### 全部支持参数
| 参数名 | 类型 | 默认值 | 作用 | 注意事项 |
| ----- | ----- | ------ | ------- | --- |
| recordOptions | Object | {duration:60000} | 录音配置 |各端支持情况不同,请自行查看[官方说明](https://uniapp.dcloud.net.cn/api/media/record-manager.html#getrecordermanager) |
| btnStyle | Object | 请查看源码 | 按钮样式 |对象格式 |
| btnHoverFontcolor | String | #000 | 按钮长按时文字颜色 | |
| btnHoverBgcolor | String | whitesmoke | 按钮长按时背景颜色 | |
| btnDefaultText | String | 长按开始录音 | 初始按钮文字 | |
| btnRecordingText | String | 录音中 | 录制时按钮文字 | |
| vibrate | Boolean | true | 震动反馈 | 弹窗、滑动取消时 |
| popupTitle | String | 正在录制音频 | 弹窗顶部文字 | |
| popupDefaultTips | String | 松手完成录音 | 录制时弹窗底部提示 | |
| popupCancelTips | String | 松手取消录音 | 滑动取消时弹窗底部提示 | |
| popupMaxWidth | Number | 600 | 弹窗展开后宽度 |注意这里几个单位都是rpx |
| popupMaxHeight | Number | 300 | 弹窗展开后高度 | |
| popupFixBottom | Number | 200 | 弹窗展开后距底部高度 | |
| popupBgColor | String | whitesmoke | 弹窗背景颜色 | |
| lineHeight | Number | 50 | 声波高度 | |
| lineStartColor | String | royalblue | 声波波谷时颜色色值 | 色值或者颜色名均可 |
| lineEndColor | String | indianred | 声波波峰时颜色色值 | |
### 原作者其他插件
- [bwinBrand多端自适应企业官网、uniCloud云端一体【用户端】](https://ext.dcloud.net.cn/plugin?id=7821)
- [bwinBrand多端自适应企业官网、uniCloud云端一体【管理端】](https://ext.dcloud.net.cn/plugin?id=7822)
- [bwinAgent多端、多项目全民经纪人、uniCloud云端一体【经纪人端】](https://ext.dcloud.net.cn/plugin?id=8606)
- [bwinAgent多端、多项目全民经纪人、uniCloud云端一体【管理员端】](https://ext.dcloud.net.cn/plugin?id=8607)
- [必闻优学,教育培训机构模板(单校区版,纯模板)](https://ext.dcloud.net.cn/plugin?id=7709)
### 一个有趣的社区
- [NeverBug.cn 弹幕式互动社区](https://neverbug.cn)
### 原作者
- QQ123060128
- Emailkarma.zhao@gmail.com
- 官网https://brand.neverbug.cn
### 补充作者
- QQ27836407
- Emailzgdabao.zhao@gmail.com