更新记录
0.5.0(2024-12-31)
首发版本 v 0.5.0
平台兼容性
Vue2 | Vue3 |
---|---|
√ | √ |
App | 快应用 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节小程序 | QQ小程序 |
---|---|---|---|---|---|---|
HBuilderX 4.31,Android:5.0,iOS:不支持,HarmonyNext:不支持 | × | × | × | × | × | × |
钉钉小程序 | 快手小程序 | 飞书小程序 | 京东小程序 | 鸿蒙元服务 |
---|---|---|---|---|
× | × | × | × | × |
H5-Safari | Android Browser | 微信浏览器(Android) | QQ浏览器(Android) | Chrome | IE | Edge | Firefox | PC-Safari |
---|---|---|---|---|---|---|---|---|
× | × | × | × | × | × | × | × | × |
bsx-voicechat-wx 一款类微信发语音的页面组件,适用于移动端聊天发语音
重要
- 此组件基于bsx-recorder-api插件开发,使用前需先购买插件.bsx-recorder-api
- 暂仅支持Android 端
- 因使用bsx-recorder-api, 故需要打自定义基座
- 除录音和播放操作外,本组件使用uts相关api的能力,放心使用.
开发文档
作者
aoaobaba 傲傲爸爸~
介绍
基于bsx-recorder-api 开发的类微信发语音界面。
功能
- [X] 长按唤起语音界面说话录音
- [X] 滑动到响应的位置指定操作
- [X] 取消语音
- [X] 播放已录制语音
- [X] 松手发送语音
- [X] 手动发送语音
- [X] 短时间录音无效
说明
本组件插件依赖bsx-recorder-api, 使用前请先购买授权并导入项目。 组件不能脱离bsx-recorder-api 单独使用!!
布局:以下是组件在项目页面中的位置示意。可以看到,组件需要放置在一个dialogPage里; dialogPage用于遮罩整个app,起到遮罩层的功能。
事件:app的page里面需要设置用户的touch操作相关的事件,并触发组件专有的事件;dialogPage里面监听事件后再调用组件暴露的专有事件,组件在接收到处理请求时 开始处理相应的业务逻辑。例如,用户touchmove事件时,组件会根据手指的移动位置,分别响应处理【取消】或【更多操作】;当组件处理了事件后,dialogPage里可以 callback再进行处理。例如,在组件中提供了@closeCallback,用于关闭时在dialogPage内提供更多操作,关闭时会返回录音文件全路径
注意!!本组件没有提供dialogPage, 但是依赖一个承载的父页面,你可以把组件直接放在Page里,但那样效果很糟糕。 下面示例会提供作者项目内的dialogPage相关代码,以供参考!
类型
type bsxVoiceChatWxComUiStyle = {
visualizerViewBackgroundColor?: string // 可视化视图背景色
visualizerBaseColor?: string //可视化颜色
}
type bsxVoiceChatWxComFuntions = {
enableAudioVisualizer?: boolean //是否启用音频实时 可视化
maxDuration?: number // 最大录音时长,默认最大30分钟 超过自动停止
silenceThreshold?: number // 静音检测阈值
silenceThresholdMs?: number // 静音检测持续时长阈值
}
组件参数
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
pageInstance | UniPage | 必须传 | 组件的父页面实例 |
uiStyle | bsxVoiceChatWxComUiStyle | 参考类型 | 组件UI |
comFunctions | bsxVoiceChatWxComFuntions | 参考类型 | 组件功能性属性 |
@closeCallback | callback: () => UTSJSONObject | 可选 | 组件关闭时的回调里面包含了3种action值,分别为cancel,send,tooShort |
@onStartRecord | .. | 可选 | 开始录音回调 |
@onRecording | .. | 可选 | 录音中回调 |
@onFailRecord | .. | 可选 | 录音失败回调 |
@onStopRecord | .. | 可选 | 录音停止回调 |
@onStartPlay | .. | 可选 | 开始播放回调 |
@onStopPlay | .. | 可选 | 停止播放回调 |
@onEndPlay | .. | 可选 | 播放完回调 |
组件方法
通过组件的Ref 引用,可以调用!
方法名 | 参数 | 功能 | 调用示例 |
---|---|---|---|
show | 无 | 组件展现 | Ref.value?.show?.() |
close | 无 | 关闭组件 | |
actionsAnimatingByTouchMoveEvent | (screenX, screenY) | 处理touchmove事件 | |
actionsConsumeTouchEndEvent | (screenX, screenY) | 处理touchend事件 | 例如松开发送,或指定区域的操作 |
actionsConsumeTouchCancelEvent | 处理意外中断touch事件 | 例如系统行为中断了用户的touch事件时触发 |
使用
- 新建一个页面并注册,作为dialogPage使用,用做遮罩层
- 在dialogPage里面放置bsx-voicechat-wx组件,并设置相关属性或回调处理
- 在业务页面响应用户操作并打开dialogPage显示组件,业务页面需要触发touch相关的组件专用事件!
步骤一、二 新增的页面并注册到page.json 用于dialogPage
<template>
<!-- !!遮罩层 当前页是一个dialogpage 页面 用于展现BsxRecorderView 组件时 的应用界面遮罩
* https://doc.dcloud.net.cn/uni-app-x/api/dialog-page.html
* 1. 初始化时 给了全屏的宽高
* 2. 设置了背景色 background-color: rgba(0, 0, 0, .82)
* 3. 其父页面为调用打开当前dialog的页面
* 4. bsx-recorder-view 组件放置在此dialog page 内
* 5. 当使用类wechat的触摸操作语音的模式时,注意!!!!父组件的touchmove和touchend 事件触发是必要的!因为组件需要响应触摸处理!
* 6. 与父页面的通信 使用事件总线!如果添加了事件触发和订阅,请在适当时机off掉!
*
* 更多信息请参阅组件插件的开发文档
-->
<view id="bsx-recorder-demo" class="" style="background-color: rgba(72, 72, 72, .96);" :style="`width:${screenWidht};height:${screenHight}`">
<!-- bsx 仿微信语音聊天组件 -->
<bsx-voicechat-wx ref="bsxVoicechatWxRef" :pageInstance="dialogPage" :uiStyle ="recorderUi" :comFunctions="recorderFun" @closeCallback="closePage"
@onStartRecord="onStartRecord" @onStopRecord = "onStopRecord"
></bsx-voicechat-wx>
</view>
</template>
<script setup lang="uts">
import {bsxVoiceChatWxComUiStyle, bsxVoiceChatWxComFuntions } from '@/uni_modules/bsx-voicechat-wx/components/bsx-voicechat-wx/bsxVoiceChatWxType.uts'
const recorderUi = ref<bsxVoiceChatWxComUiStyle>({
visualizerViewBackgroundColor: '#95ec6a', // #95ec6a wechat可视化语音背景色
visualizerBaseColor: '#575D57' // 音效柱状图颜色 支持安卓Color支持的色值: 参考 转换失败将使用默认值
// https://developer.android.google.cn/guide/topics/resources/more-resources?hl=zh-cn#Color 参见官方Color 文档
})
const recorderFun = ref<bsxVoiceChatWxComFuntions>({
enableAudioVisualizer: true, // 是否启用实时音频可视化
maxDuration: 30 * 60 * 1000, // 最大录音时间 超过则自动停止录音
silenceThreshold: 50, // 静音检测阈值 低于此值时 silenceThresholdMs 将会作用,例如静音超过silenceThresholdMs时长 则自动停止录音!
silenceThresholdMs: 10 * 1000 // 静音持续时长阈值
})
// bsx-voicechat-wx 组件引用
const bsxVoicechatWxRef = ref<BsxVoicechatWxComponentPublicInstance | null>(null)
const screenHight = ref(0)
const screenWidht = ref(0)
// 当前的dialog page 实例
const dialogPage = ref<UniPage | null>(null)
onLoad(() => {
console.log('设备信息', uni.getWindowInfo())
let wi = uni.getWindowInfo()
screenHight.value = wi.screenHeight -45
screenWidht.value = wi.screenWidth
})
// 进行一些必要实例获取
onReady(() => {
const currentInstance = getCurrentInstance()
dialogPage.value = currentInstance?.proxy?.$page
console.log("dialog page 的主页面实例", dialogPage.value)
// 打开录音机组件 这一定要稍后等待获取 dialogPage 实例完成
setTimeout(() => {
bsxVoicechatWxRef.value?.show?.()
}, 300)
// 订阅父页面的相关touchmove 事件
uni.$on('bsxRecorderViewParentPageTouchMove', (screenX:number, screenY: number) => {
// 组件内部会响应touch move 的changed X Y值进行相应的功能处理
bsxVoicechatWxRef.value?.actionsAnimatingByTouchMoveEvent?.(screenX, screenY)
})
// 订阅父页面的touchend 事件
uni.$on('bsxRecorderViewParentPageTouchEnd', (screenX:number, screenY: number) => {
// 组件内部会响应touch emd 的changed X Y值进行相应的功能处理 wechat 模式时 取消发送语音 或 更多操作
bsxVoicechatWxRef.value?.actionsConsumeTouchEndEvent?.(screenX, screenY)
})
// 订阅父页面的touchcancel 事件
uni.$on('bsxRecorderViewParentPageTouchCancel', () => {
bsxVoicechatWxRef.value?.actionsConsumeTouchCancelEvent?.()
})
})
// 关闭dialog page
const closePage = (json:UTSJSONObject) => {
// 触发关闭的action
console.log(`关闭事件action: ${json["action"]} - ${json["filePath"]}`)
// 给录音机组件一个 动画处理的时间
setTimeout(() => {
uni.closeDialogPage({
success: () => {
console.log("dialogPage 已关闭")
},
fail: (err) => {
console.log("dialogPage 关闭错误", err)
},
complete: (res) => {
console.log("dialogPage 关闭完成", res)
}
})
}, 300)
}
const testClick = () => {
bsxVoicechatWxRef.value?.show?.()
}
const touchStart = (e: UniEvent) => {
// console.log("触发了touchstart 事件", e)
}
const touchMove = (event: UniTouchEvent) => {
console.log("触发了touchMove 事件", event.changedTouches[0])
return true
}
const touchEnd = () => {
console.log("结束触摸")
}
const touchCancel = () => {
console.log("取消触摸")
}
const onStartRecord= () => {
console.log('page页面接收到回调,开始录音了---------------')
}
const onStopRecord= () => {
console.log('page页面接收到回调,录音停止了---------------')
}
</script>
<style>
</style>
步骤三 业务页面的处理
<template>
<!-- #ifdef APP -->
<scroll-view style="flex:1">
<!-- #endif -->
<button @click="removeFile">删除文件</button>
<view class="chat-box">
<view class="" v-for="i in 3">
<view class="bsx-chat" style="margin-top: 10;">
<view class="" style="margin-left: 10;border-radius: 30;height: 45;width: 45;">
<image src="/static/BSX_mini.png" mode="" style=" height: 45;width: 45;"></image>
</view>
<view class=""
style="margin-top: 15; height: 30; margin-left: 10;padding: 3;border: 1px solid gainsboro;border-radius: 20;">
<text style="padding: 5;font-size: 15;">BSX 专注uniapp X的Bootstrap框架。</text>
</view>
</view>
<view class="me-chat">
<view class=""
style="margin-top: 15; height:60; margin-right: 10;padding: 3;border: 1px solid gainsboro;border-radius: 20;">
<text style="padding: 5;font-size: 15;width: 160;">清风徐来,水波不兴;
好好学习,天天向上。</text>
</view>
<view class="" style="margin-right: 10;border-radius: 30;height: 45;width: 45;">
<image src="/static/BSX_mini.png" mode="" style=" height: 45;width: 45;"></image>
</view>
</view>
</view>
</view>
<view @longpress="longPress" @touchstart="touchStart" @touchcancel="touchCancel" @touchmove="touchMove" @touchend="touchEnd" class=""
style="width: 100%;display: flex;justify-content: center; position: fixed;height: 45;bottom: 0; background-color: ghostwhite;">
<text style="text-align: center; font-size: 16;">长按说话</text>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup>
const touchLongTimer = ref(0)
const touching = ref(false)
const touchingTime = ref(0)
onMounted(() => {
})
const removeFile = () => {
const fileManager = uni.getFileSystemManager()
// 删除文件示例
fileManager.removeSavedFile({
filePath: '/storage/emulated/0/Android/data/uni.UNIC8B85B9/files/xxx/xxx_20241231_134932.aac',
success: () => {
console.log('移出文件 成功')
},
fail: (res: IFileSystemManagerFail) => {
console.log('发生错误', res)
}
})
}
// 打开dialogPage
const openRecorder = () => {
const pages = getCurrentPages()
// 获取当前页面
const page = pages[pages.length - 1]
uni.openDialogPage({
url: '/pages/bsx-recorder-wechat/bsx-recorder-wechat',
animationType: 'fade-in',
success: () => {
console.log('dialog open-------------------')
},
fail: (res) => {
console.log('dialog open fail--------------', res)
},
animationDuration: 300,
parentPage: page
})
}
// 响应touch cancel事件,一般表现为用户touch事件被意外阻断
const touchCancel = (event : UniTouchEvent) => {
touching.value = false
touchingTime.value = 0
console.log("取消 touch")
// 需要发送触摸事件被系统中止的事件给到组件 处理
uni.$emit('bsxRecorderViewParentPageTouchCancel')
}
// 长按打开
const longPress = (event : UniTouchEvent) => {
console.log("longPress", event)
openRecorder()
}
// 用户toucmove 事件触发 组件专用的事件
const touchMove = (event : UniTouchEvent) => {
// 触发touchmove 监听
uni.$emit('bsxRecorderViewParentPageTouchMove', event.changedTouches[0].screenX, event.changedTouches[0].screenY)
}
//
const touchStart = () => {
console.log('父页面 touch start ---------------------')
}
// 用户touc end事件 触发组件专用的事件处理
const touchEnd = (event : UniTouchEvent) => {
console.log('父页面 touch end ---------------------')
uni.$emit('bsxRecorderViewParentPageTouchEnd', event.changedTouches[0].screenX, event.changedTouches[0].screenY)
}
// 释放监听器
onUnmounted(() => {
uni.$off('bsxRecorderViewParentPageTouchMove')
uni.$off('bsxRecorderViewParentPageTouchEnd')
})
</script>
<style>
.chat-box {
display: flex;
flex-direction: column;
}
.bsx-chat {
display: flex;
flex-direction: row;
align-self: flex-start;
height: 90;
}
.me-chat {
display: flex;
flex-direction: row;
align-self: flex-end;
height: 90;
}
</style>
以上,为使用组件的完整示例。主要是通过dialogPage实现的应用遮罩功能,主要的区别在于组件的父组件是dialogPage 而不是你的业务页面!而业务页面变成了 dialogPage的父组件! 有关dialogPage, 请参阅官方文档dialogPage ,进一步了解。
权限
需要请求录音权限!!!
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>
tips
- 重要!!!!! 需要录音权限!请配置清单。
- 需要自定义基座
- 其他问题,欢迎指正
Happy new year ! 2025
谢谢!