IM_自定义audio播放消息

做即时通讯,除了文字、图片、表情、还有媒体消息,整理一下制作过程中自定义聊天框中的audio

效果图

在这里插入图片描述

tsx完整代码

AzEventBus 是解决点击多个语音播放时候,保证只有一个在播放;没什么特别的,就是自己简单封装了个EvenBusAzEventBus

import {useRef, useEffect, useState, memo} from 'react';
import IconfontSvg from '@/views/Components/IconfontSvg';
interface PropsType {
	// 播放地址
    url: string,
    // 允许父组件把class传入进来
    className?: string,
    // 宽度
    width?: number,
    // 高度
    height?: number,
   	// 是否是自己发送的消息,就像微信/钉钉;右侧是自己发消息左侧是别人发布消息;
    isSelfSend: boolean,
    // 消息唯一id, 用来比对当前全局在播放的是不是自己,不是自己就暂停自己
    mid: string
}
import AzEventBus from '@/views/Utils/EventBus';

import style from './lazyAudio.module.less';
// preload="metadata" 
// 辅助函数:格式化时间为'MM:SS'或'HH:MM:SS'  
const formatTime = (time: number) => {  
    const hours = Math.floor(time / 3600);  
    const minutes = Math.floor((time % 3600) / 60);  
    const seconds = Math.floor(time % 60);  
    // if (hours > 0) {  
    //   return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;  
    // } else {  
    //   return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;  
    // }  
    if (hours > 0) {
        return `${String(hours)}${String(minutes)}${String(seconds)}`;
    } else {
        // return minutes > 0 ? `${String(minutes)}分${String(seconds)}秒` : `${String(time.toFixed(2))}秒`;
        if (minutes > 0) {
            return `${String(minutes)}${String(seconds)}`;
        }
        // 如果是整数秒时
        if (Number.isInteger(time)) {
            return time;
        }
        // 如果是小数,检查小数点后的位数
        const decimalPart = time.toString().split('.')[1];
        const decimalLength = decimalPart ? decimalPart.length : 0;
        /* 如果小数点后超过两位,则四舍五入到两位小数
            如果小数点后不足两位,则直接转换为字符串,不补零
        */
        return decimalLength > 2 ? `${time.toFixed(2)}` : `${time.toString()}`;
    }
};
const LazyAudio = ({url, className= '', width = 300, height = 54, isSelfSend, mid}: PropsType) => {
    const currentRef: any = useRef(null);
    const audioRef = useRef<any>(null);
    const [isPlaying, setIsPlaying] = useState(false); 
    const [duration, setDuration] = useState('0');

    useEffect(() => {
        /* <audio>
            src: 路径
            autoplay: 自动播放
            autobuffer: 自动缓存
            loop: 循环播放
            currentTime: 播放的时间
            muted: 静音
            preload: 预加载 'auto'_指示一旦页面加载,则开始加载音频 | 'metadata'_当页面加载后仅加载音频的元数据 | 'none'_页面加载后不应加载音频
        */
        /*
            只读属性
            只读属性 currentTime 表示当前播放的时间(以秒为单位)。
            只读属性 duration 表示音频的总时长(以秒为单位)。
            只读属性 paused 表示音频是否正在暂停。
            只读属性 ended 表示音频是否已经播放完毕。
            只读属性 palyed 表示音频是否已经播放。
            只读属性 loop 表示音频是否循环播放。
            只读属性 error 表示音频的错误信息。
            只读属性 currentSrc 表示当前正在播放的音频源。
            只读属性 defaultPlaybackRate 表示音频的默认播放速度。
            只读属性 playbackRate 表示音频的当前播放速度。
            只读属性 volume 表示音频的音量(0.0 到 1.0 之间)。
            只读属性 networkState 表示音频的网络状态。
            只读属性 readyState 表示音频的就绪状态。

        */
        const audioElement: HTMLAudioElement = audioRef.current;  
        // 时长
        const loadedmetadataEvent = (event: any) => {
            const audio = event.target;
            const audioDuration = audio.duration;
                if (audioDuration === Infinity) {
                    audio.currentTime = 1e101;
                    audio.ontimeupdate = () => {
                        audio.ontimeupdate = () => { return; };
                        audio.currentTime = 1e101;
                        audio.currentTime = 0;
                        const duration = audio.duration;
                        // console.log('计算时长', formatTime(duration));
                        setDuration(duration.toFixed(2));
                    }
                } else {
                    const duration = audio.duration;
                    setDuration(duration.toFixed(2));
                    // console.log('计算时长', formatTime(duration));
                }
          
        };
        // 播放进度
        const timeupdateEvent = () => {
            // console.log('audioElement.currentTime', audioElement.currentTime, audioElement.duration);
            //   setCurrentTime(audioElement.currentTime);  
        };
        // 播放完毕
        const onAudioEndedEvent = () => {
            // 在这里添加您的逻辑,例如播放下一个音频或执行其他操作
            setIsPlaying(false);
        };
        // 播放错误
        const playErrorEvent = () => {
            //   setHasError(true); 
            setIsPlaying(false); 
        };
        // 播放_回调
        const handlePlayEvent = () => {
            // 可以在每次播放的时候从头播放
            audioElement.currentTime = 0;
            // 发布当前在播放的消息id
            AzEventBus.emit('im_msg_audio_play', {mid});
        };
        // 暂停_回调
        const handlePauseEvent = () => {
            // console.log('音频暂停', mid);
        };
        // 如果当前audio存在就绑定事件
        if (audioElement) {
            audioElement.addEventListener('loadedmetadata', loadedmetadataEvent);  
            audioElement.addEventListener('timeupdate', timeupdateEvent);  
            audioElement.addEventListener('error', playErrorEvent);  
            audioElement.addEventListener('ended', onAudioEndedEvent);
            audioElement.addEventListener('play', handlePlayEvent);
            audioElement.addEventListener('pause', handlePauseEvent);
        };
        // 如果有别的在播放就停止当前消息播放
        const imMsgAudioPlay = (msg: {mid: string | number}) => {
            if (msg.mid === mid) {
                return;
            }
            if (audioElement && audioElement.played) {
                audioElement.pause();
                audioElement.currentTime = 0;
                setIsPlaying(false);
            }
        };
        AzEventBus.on('im_msg_audio_play', imMsgAudioPlay);
        return () => {  
            AzEventBus.off('im_msg_audio_play', imMsgAudioPlay);
            if (!audioRef.current) {
                return; 
            };
            audioElement.removeEventListener('loadedmetadata', loadedmetadataEvent);  
            audioElement.removeEventListener('timeupdate', timeupdateEvent);
            audioElement.removeEventListener('error', playErrorEvent);
            audioElement.removeEventListener('ended', onAudioEndedEvent);
            audioElement.removeEventListener('play', handlePlayEvent);
            audioElement.removeEventListener('pause', handlePauseEvent);
        };  
    }, []);
    const handlePlay = () => {  
        if (!isPlaying) {
          audioRef.current.play()
            .then(() => setIsPlaying(true))  
            .catch(() => {  
              setIsPlaying(false);
            });  
        } else {  
          audioRef.current.pause();
          setIsPlaying(false);
        }  
    };
    return (
        <div ref={currentRef} className={`${style.root} ${isSelfSend ? style.cur_audio_play_right : ''}`} style={{width: `${width}px`, height: `${height}px`}}>
            <audio ref={audioRef} src={url} controls
                controlsList="nodownload noplaybackrate"
                preload="metadata"
                style={{display: 'none'}}
            >浏览器不支持该格式音频文件</audio>
            <div className={`${style.cur_audio_play_wrap}`} onClick={handlePlay}>
                <div className={style.cur_audio_play_time_wrap}>
                    <span className={style.duration}>{duration}''</span>
                    <span className={style.yuyin_svg}>
                        <IconfontSvg icons={['icon-yuyin']} className={`${style.yuyin_icon_svg} ${isPlaying ? style.yuyin_icon_svg_playing : ''}`}></IconfontSvg>
                    </span>
                </div>
            </div>
        </div>
    )
}
export default memo(LazyAudio);
样式

自己发消息是.cur_audio_play_right;是偏右侧的
别人发的消息是左侧;
时间和播放是使用了浮动;

在这里插入图片描述

.root{
    text-align: left;
    max-width: 300px;
    min-width: 150px;
    width: auto !important;
    .cur_audio_play_wrap{
  		cursor: pointer;
        display: inline-block;
        background-color: #1E5D94;
        height: 36px;
        width: 150px;
        border-radius: 0px 4px 4px 4px;
        .cur_audio_play_time_wrap{
            clear: both;
            padding: 4px 10px 4px 10px;
            color: #fff;
            .duration{
                float: right;
            }
            .yuyin_icon_svg{
                width: 20px;
                height: 20px;
                fill: #fff;
                position: relative;
                top: 4px;
            }
            .yuyin_icon_svg_playing{
                animation-name: width-animation;
                animation-duration: 0.5s; /* 动画持续时间 */
                animation-iteration-count: infinite; /* 无限循环 */
                animation-timing-function: ease-out; /* 线性速度 */
                animation-fill-mode: forwards; /* 动画结束后保持最后一帧状态 */
            }
        }
    }
}
.cur_audio_play_right{
    text-align: right;
    .cur_audio_play_wrap{
        background-color: #1d79cd;
        clear: both;
        border-radius: 4px 0px 4px 4px;
        .cur_audio_play_time_wrap{
            padding: 4px 10px 4px 10px;
            .duration{
                float: left;
            }
            .yuyin_icon_svg{
                transform: rotate(180deg);
            }
        }
    }
}

@keyframes width-animation {
    from {
      width: 0;
    }
    to {
      width: 20px;
    }
}
播放图标

阿里图标,搜索 “语音消息”; 官网地址: https://www.iconfont.cn/
Demo中使用的是svg;指定了宽度、高度、颜色; 动画使用@keyframes循环宽度变化;

@keyframes width-animation {
    from {
      width: 0;
    }
    to {
      width: 20px;
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/905077.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tcp shutdown, fin_wait1, fin_wait2, close_wait, last_ack, 谢特!

TCP 作为双向传输协议&#xff0c;如果你想只收不发&#xff0c;可以单向关掉发&#xff0c;shutdown(socket.SHUT_WR)&#xff0c;但不建议这么做。 看以下代码&#xff1a; #!/Users/zhaoya/myenv/bin/python3 # client import socketclient_socket socket.socket(socket.…

怎么知道社媒上用户在讨论品牌什么?评价如何?

现在社交媒体不再仅是人们闲聊和分享生活片段的地方&#xff0c;更是品牌了解市场趋势和消费者需求的重要渠道。所以做号社媒上用户声音的聆听&#xff0c;企业更能抓住客户需求、抢占潜力市场&#xff0c;进一步占据更多市场份额&#xff0c;获得精准客户。 做好用户声音聆听…

【QT】Qt窗口(上)

个人主页~ Qt窗口 一、菜单栏二、工具栏三、状态栏四、浮动窗口五、对话框1、简介&#xff08;1&#xff09;模态对话框&#xff08;2&#xff09;非模态对话框&#xff08;3&#xff09;混合属性对话框 Qt窗口是通过QMainWindow类来实现的&#xff0c;我们之前的学习是通过QWi…

第二十章 Vue组件通信之父子通信

目录 一、引言 二、组件关系分类 三、组件通信的解决方案 3.1. 父子通信流程图 3.2. 父组件通过 props 将数据传递给子组件 3.2.1. 代码App.vue 3.2.2. 代码MySon.vue 3.3. 子组件利用 $emit 通知父组件修改更新 ​编辑3.3.1. 代码App.vue 3.3.2. 代码MySon.vue 3…

用ChatGPT提升工作效率:从理论到实际应用

伴人工智能技术的迅速演进&#xff0c;像ChatGPT这类语言模型已成为提升工作效率的关键工具。这类模型不仅具备处理海量数据的能力&#xff0c;还能自动化许多日常任务&#xff0c;从而提高决策的准确性。本文将深入探讨如何在工作中利用ChatGPT等AI工具提升效率&#xff0c;涵…

golang 服务注册与服务发现框架 入门与实践

Go语言中服务注册与发现的应用 在Go微服务架构中&#xff0c;服务注册与服务发现是实现服务间通信和解耦的关键。随着服务数量的增长&#xff0c;手动管理服务之间的依赖关系变得异常复杂且容易出错。因此&#xff0c;自动化服务注册与发现机制变得尤为重要。当一个Go微服务启…

1.STM32之定时器TIM---第一部分(基本定时器)(功能最强大结构最复杂的一个外设)(实验基本定时功能)-----定时器定时中断(利用内部时钟72M)

定时器TIM是STM32外设中功能最强大结构最复杂的一个外设&#xff01;Whappy STM32F103C8T6总共由一个高级定时器3个通用定时器 #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include &quo…

无人机救援系统基本组成

无人机救援系统基本组成 1. 源由2. 组成2.1 无人机载具2.1.1 多旋翼2.1.2 垂起固定翼2.1.3 智能避障2.1.4 物资投递 2.2 智能吊舱2.2.1 云台2.2.2 高清摄像2.2.3 红外热成像2.2.4 激光测距2.2.5 目标跟踪 2.3 通讯链路2.3.1 超长距离通信2.3.2 长距离通信2.3.3 中等距离通信 2.…

CSS 复习

复杂选择器可以通过&#xff08;id的个数&#xff0c;class的个数&#xff0c;标签的个数&#xff09;的形式&#xff0c;计算权重。 如果我们需要将某个选择器的某条属性提升权重&#xff0c;可以在属性后面写!important&#xff1b;注意!importent要写在;前面 很多公司不允许…

uniapp一键打包

1.先安装python环境&#xff0c; 2.复制这几个文件到uniapp项目里面 3.修改自己证书路径&#xff0c;配置文件路径什么的 4.在文件夹页面双击buildController.py或者cmd直接输入buildController.py 5.python报错&#xff0c;哪个依赖缺少安装哪个依赖 6.执行不动的话&…

SINAMICS V90 在汽车行业中的应用-天拓四方

随着生活水平的提高&#xff0c;平均每家每户都配有一辆代步用的小汽车&#xff0c;汽车行业也正处于蓬勃的发展中&#xff0c;尤其是新能源汽车&#xff0c;带来了新一轮的汽车生产热潮。生产一辆汽车&#xff0c;从零配件的加工&#xff0c;到整车的组装&#xff0c;基本已经…

C++算法第五天

本篇文章继续和大家一起刷算法题 第一题 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 题目解析 题目要求&#xff1a; 这是一个连续的子数组 计算子数组内元素的和&#xff0c;若数组内元素的和符合 > target的值并且该子数组的长度是最短的&#xff0c;则返回…

【电机控制器】以STC8H1K系列举例——持续更新

【电机控制器】以STC8H1K08 举例——持续更新 文章目录 [TOC](文章目录) 前言一、代填二、参考资料总结 前言 使用工具&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、代填 二、参考资料 STC8H1K系列数据手册 梁工——BLDC, 三相无…

如何快速给word文件里的文字加拼音?请看详细步骤

怎么快速给word文件里的文字加拼音&#xff1f;在日常的文字处理工作中&#xff0c;很多人可能会遇到一个问题&#xff1a;如何在Word文档中为文字添加注音。尤其是对于一些需要帮助读音的文本&#xff0c;比如中文学习材料、教材或儿童读物&#xff0c;注音可以帮助读者更好地…

AI跟踪报道第62期-本周AI新闻: 微软推出Copilot的AI Agent和Computer Control

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

C++学习:类和对象(一)

一、面向过程与面向对象编程 1. 什么是面向过程编程&#xff1f; 面向过程编程&#xff08;Procedural Programming&#xff09;是一种以过程&#xff08;或函数&#xff09;为中心的编程范式。程序被视为一系列按顺序执行的步骤&#xff0c;主要通过函数对数据进行操作 特点…

mac|maven项目在idea中连接redis

安装maven brew install maven idea-setting导入redis插件 idea新建maven项目 构建系统选择maven 项目右侧数据库图标导入redis 新建一个数据库&#xff0c;名称必须为数字&#xff0c;测试一下是否可以连接&#xff0c;连接成功后选择确定 pom.xml导入redis <depende…

开发了一个成人学位英语助考微信小程序

微信小程序名称&#xff1a;石榴英语 全称&#xff1a;石榴英语真题助手 功能定位 北京成人学士学位英语辅助学习工具&#xff0c;包含记高频单词&#xff0c;高频词组&#xff0c;专项练习&#xff0c;模拟考试等功能。 开发背景 个人工作需要提高学习英文水平&#xff…

GitLab代码仓管理安装配置使用

Gitlab介绍 GitLab是一个基于Git的开源项目管理工具&#xff0c;它集成了版本控制、代码审查、持续集成&#xff08;CI&#xff09;/持续部署&#xff08;CD&#xff09;、自动化测试等多种功能&#xff0c;是一个完整的DevOps平台。以下是对GitLab的详细介绍&#xff1a; 一…

C# Retry库

比如网络访问或硬件参数设置需要重试&#xff0c;可引用gunet上的Polly库。 同步方式&#xff08;每次重试有不同的时间间隔&#xff09; var polly Policy.Handle<Exception>().WaitAndRetry(new[] { new TimeSpan(0, 0, 1), new TimeSpan(0, 0, 2), new TimeSpan(0, …