更新记录

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的能力,放心使用.

开发文档

UTS 页面组件

作者

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事件时触发

使用

  1. 新建一个页面并注册,作为dialogPage使用,用做遮罩层
  2. 在dialogPage里面放置bsx-voicechat-wx组件,并设置相关属性或回调处理
  3. 在业务页面响应用户操作并打开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

  1. 重要!!!!! 需要录音权限!请配置清单。
  2. 需要自定义基座
  3. 其他问题,欢迎指正

Happy new year ! 2025

谢谢!

隐私、权限声明

1. 本插件需要申请的系统权限列表:

// 使用录音功能,需要 <uses-permission android:name="android.permission.RECORD_AUDIO" />

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问