多条语音汇成一整条开发

最近在做一个多条语音合成一整条语音并且结合进度条可以快进或者后退功能,功能不复杂,但是所遇到的坑不少,所以我就想着把我遇到的坑写下来,希望以后有用到的小伙伴们可以少走点弯路:

chatgpt 连续对话接口 播放连续对话_移动开发

功能技术点


  • 多条语音连续播放;
  • 暂停或者继续播放;
  • 进度条与时间动画;
  • 拖拽小按钮可以快进或者后退并且定位到相对应语音秒数;

html+css代码:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <link rel="stylesheet" type="text/css" href="resets.css"/>
        <script src="jquery-3.1.1.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="flexible.js" type="text/javascript" charset="utf-8"></script>
        //第一步:首先加载一个微信JS-SDK
        <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
        <style type="text/css">
            .duration-time-container{
                height: 3rem;
                width: 100%;
                padding-top: 3.666rem;
            }
            .control-bar{
                position: relative;
                height: 4px;
                background: #ebebeb;
                border-radius: .133333rem;
                width: 70%;
                margin: 0 auto;
            }
            #slider{
                position: absolute;
                display: block;
                top: 50%;
                transform: translate(-50%,-50%);
                   cursor: pointer;
                   width: 0.4rem;
                height: 0.4rem;
                left: 0;
                -webkit-border-radius: .4rem;
                border-radius: .4rem;
                background: #fff;
                border: 1px solid #e6e6e6;
                -webkit-box-shadow: 0 0.04rem 0.066667rem 0 hsla(0,0%,90%,.8);
                box-shadow: 0 0.04rem 0.066667rem 0 hsla(0,0%,90%,.8);
            }
            #slider::after{
                display: block;
                content: "";
                width: 1.6rem;
                height: .8rem;
                opacity: 0;
                position: absolute;
                display: block;
                top: 50%;
                left: 50%;
                transform: translate(-50%,-50%);
            }
            #playing-bar{
                display: block;
                position: absolute;
                width: 0;
                height: 100%;
                -webkit-border-radius: .266667rem;
                border-radius: .266667rem;
                background: #ef5670;
            }
            .play-cont{
                margin-top: 0.8rem;
                text-align: center;
            }
            .on-log{
                width: 1.653333rem;
                height: 1.653333rem;
                background-size: 100%;
                display: inline-block;
                cursor :pointer;
            }
            .btn-play{
                background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHwAAAB8CAMAAACcwCSMAAAA81BMVEUAAAD7PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF37PF3+3uP7RWT8e5L8UW77PmD/+Pn/8fT/6+7+197+u8f9sL39o7L9lqj9iZ38b4j8Wnb+y9T+ydP8Y32cYNPOAAAAPHRSTlMAAgYKFA0QGiAoHRcwIyw3PTolQzRGSU5BMvZWUkz56vS85bbTo2/bxKqU8YJ6dmVhXVRQr+HMj2uaiFhUuNt3AAAGqUlEQVRo3rzUa1PaQBQG4N7ETSBEkAyXDyFAqMYEZxhuHTmobbX2avv/f01zZqMnSyJkl43vN3XGJ+fdPftGKW8zkfwHGlSJb9Drvkul5C/IqHkpxc/A73OS+QC9dAo+wlSSHGHoA8jXRj/DXP2wFf4N+HfB10KTzN1qtXqcSvwj/wLyiddDcxlZ0zSZkPgX+Anc18ILNJdjGN3WNBoP/EXYA+iFC38wjqYtxvADEl/gNdBcZuw0Gg0hJ8NRdMoY9w/lszTKzsUCdmRx4aCf5RXHxrNO6G7kw974UTfh8expeBWbaCdwoVDcwCFeXhfoCqfnIJER5ysCL29z2h67IBV3bHNe0BVsZk1CkE44sZisTjY/7XjszhKUsuzEw/OTJ13CxrEvPVCMd4nDS+hi5SarB3BAgjozxeoLz31sWg0fDop/YpnHwuzF7fYQDsywXVjfstchHJxwvaUXspk17YGG9KYWE/RC9hptDfHWRfTkoiedzzzQFG/Gm3+68rsG53anD9rS73A9M3q2dNxvq+aDxvg1C/f95eKfDxxtYw5aMzdQfz72XaUzYwKaMzHYjmNP203H0417TpPrebhw4PUlaM+yLhx7/uDVuPQISkgUF1+l0fMGx9JPQigh4QkWT6NncH7TAyglAb/xGVy46W0XSonbNsQ7lx28WR9BSRnVm3mjpwefQWmZpUfPHXwOpWUujp4dvONCaXE7OaOnB19BiVmlR6fWkx03agsoMYuakew6mkLr+LBOYX9ubj+DYqb4yFLvhOPjVhvD/jxsNt8V+XENnzkRp9aHsD/Xm83m648bUMjwqXfCqXVa8p045u6nCj9L9b7d+hVgCuCY+19fQDZXYu8cT1ofQGEcc30riw947wKetN7qy+CYb7/l8H4r6f0JpxfGATkc8/hPSnfonUnh2PonCZzy8EcC/4i9E5468pUSLrX2Kzp0xJMtNy2jGyjhUmsfdA3LTDZdvG/nSrjU2p8LNw7x5L7ZZ0q41Nqf2fzGiXj8xNhDJVxq7ft2/MwQTpfd9pRwqbX3bLruAt7oqeKUxz1712tk8ArHQRmn3O05eY5XysHv5XFttf8tVHsW7zZe48I1ui/g/7s3l+S2YRgMr72QRZoW9a4fimVXHVuqkknijHY9QO9/mwKWGoYDs40e9NTFxjt//AGKBElgdYNPrYNTt99ikTHG/BbLqxH+ZH9jeergdJEp7G+pBTescO6psZ5MnFwF1zeWZAD8Z780Kuk2FrqlMvsJJKNbapdMMPupM/uYTGhpFCsay4eGgmlplPat4SHV6nHpO9cSSG26B7YPioFLUmc147T81XhE/jH0iPy1nW8Krs04dWywcjnwquYbPShysW0s2lZwclBUR2Q3ODcW7ayFHOEYdOX32OZVWKy8DnByLcJF1lizTHByLaL5nZWNNSuZSy+EPvg9ZOLQWLKDYCG9CvvodzcobV15l4GrXQLS688l8yxFPfPYklx/Kr930uN1Y8HW8W/hBK5JLxoLVujCOziVLh+mZz9IIvyqdC6qaGp2VAlOhSu4ks68l6nhLx5TwhWcPm2FXMjNtOyNFDykT1v0UQ/nnEj2U7L3iYDZRh/16HNm6/hqysf7Cp1OnzOp9NbxgcyjySZbLoPO6VQ4fcKeY9hP0UTsEwZ8Tp6wCbyjz/B7k8doEvZR4ldGHu/NZQsYdpGi9vG6UwEBJ2ULpoINRc9Xo+dartikYMNYqtLSq/3Ib6xq2cZSFRp2RZfxZtTaEkuN3cI/U57U0v0iGhzuwm/ZA4qjkM6B/vowcB97BTb/FJvS0fNLHnhpco4GyD4nqRfwJfqcsE1wnQ6rDbq+2vQtPd1U6HJYW3Q2wvvQIfAgPj/0YR9ykA3h7sWmBZgLdD2KT3Ki3qg6T1A2unxBCjD7lZ6ieIg84Mvd9u/o7a4ENEQbZZPS095FtyjeZRd8fcxWf1zPsmN9QTMXZfctulV0TfwFL2TqJ3G+O1wdwLfDLo8TP5XigkbZExQ7o/gWD1MP+XVcvu2y5/1q/QUivF7tn7PdWxnXSIZp1qJBNmX3pis8xB7kI99/TOo6fre6Th59JINoiLVCE/bQ4nrEg3zOcACelGnqd5amUnoIZhxEt2haXD+yrcDp+DAAHAEaMMGAi+CO7BjaCkY3VDiz2RwGEMIIXP5uLnBDAM9nM8fQUDFNKwnqb0eAFobtL3JBs6mVZNImGsdZwBCULRaOY26iub/2oUkap+61ZWxcs9y9twkOapD8f1pDr4/gX2nMHfQ3vwCpmKm0f8qSEwAAAABJRU5ErkJggg==) 50%;
            }
            .btn-pause{
                background: url(data:image/png;base64,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) 50%;
            }
            
        </style>
    </head>
    <body>
        <p class="cont"></p>
        <div class="duration-time-container">
            <div class="control-bar">
                <span id="playing-bar" ></span>
                <span id="slider"></span>
                
                <div class="time">
                    <div></div>
                    <div></div>
                </div>
            </div>
        </div>
        <div class="play-cont">
            <span class="btn-play on-log" data-log = "播放"></span>
        </div>
        <!--语音标签-->
        <audio  id="first-audio" preload="auto"></audio>
        <script src="allVoice.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            $(function(){
                getDistance("slider","playing-bar");
                //用于微信浏览器自动播放
                 if(isWeiXinClient()){//检测微信环境
                    wx.ready(function() {
                         playAudio(startSeconds,currtIndex,result);
                     });
                }
            });
            
        </script>
    </body>
</html>
js代码
数组结构:
{
    "id":299,
    "userId":22,
    "fileUrl":url,
    "seconds":13//每条语音的时间长度
}
//请求语音数组
var result = res.result;
//语音总长度
var voiceLength = 0;
//语音对象的秒数
var voiceSecond = 0;
//进度条动画
var progress;
//进度条长度
var width = $(".control-bar").width();
//拖拽时需要减去左边距,由于我的进度条长度为70%,所以marginleft值为body宽的百分15
var marLeft = $("body").width()*0.15;
//语音播放到多少秒用于寻找对应的语音消息
var currentVoiceLeg = 0;
//语音位置,单位百分比
var voicePosition;
//定义移动值
var moveleft = 0;
//语音开始播放位置
var startSeconds = 0;
//语音是否在播放
var isPlay = false;
//是否结束
var isOver = true;
for(var i=0;i<result.length;i++){
    result[i]["startLength"] = voiceSecond;
    voiceSecond+=result[i].seconds;
    result[i]["endLength"] = voiceSecond;
    voiceLength += result[i].seconds;
};
//声音百分比
var voicePercent = Number((voiceLength/100).toFixed(2));
//百分比/声音秒
var secondNum = width/voiceLength;
//当前播放audio
var currut_audio = $("#first-audio")[0];
//初始化秒数
 $(".voice-length").text(getVoiceTime(voiceLength));
 $(".voice-currtTime").text(getVoiceTime(currentVoiceLeg));
/*
 * 格式化秒数
 * voiceLength 秒数
 */
function getVoiceTime(voiceLength){
    var leng = parseInt(voiceLength);
    if(leng < 60){
        if(leng<10){
            return "00:"+"0"+leng;    
        }else{
            return "00:"+leng;
        }
    }else{
        var mint = parseInt(leng/60),second = leng-mint*60;
        if(mint<10){
            mint = "0"+mint;
            
        };
        if(second<10){
            second = "0"+second;
        };
        return mint+':'+second;    
        
        
    }
};
/*拖拽效果*/
/*
 * 获取拖拽距离
 * @param  string btnId拖拽元素id值
 * @param  string progId进度条id值
 */
function getDistance(btnId,progId){
    var btn = document.getElementById(btnId);
    btn.addEventListener("touchmove",function(evt){
        evt.preventDefault();  
        if (evt.targetTouches.length == 1) {  
            var touch = event.targetTouches[0];  
            //移动值应该减去拖拽块1/2宽度与距离最左边的值
            moveleft = touch.pageX-marLeft;  
            voicePosition = (moveleft/width)*100;
            if(moveleft<=0){//拖拽块的left值最小是0;  
                voicePosition = 0;  
                moveleft = 0;
            };  
            if(moveleft>=parseInt(width)-10){//拖拽块的left值最大是拖拽区的width减去拖拽块的width;  
                voicePosition = 100;
                moveleft = parseInt(width)-10;
            }  
           setProgressBar(btnId,progId,voicePosition+"%");
       };  
      
    },false);
    btn.addEventListener("touchend",function(evt){
        isPlay = false;
        currentVoiceLeg = Number(voicePercent*voicePosition);
        findCurrentVoice(result);
         $(".voice-currtTime").text(getVoiceTime(currentVoiceLeg));
    },false);
};


/*
 * 动态设置拖拽元素left 和进度条长度
 * @param  string btnId拖拽元素id值
 * @param  string progId进度条id值
 * @param  string value需要设置的值
 */
function setProgressBar(btnId,progId,value){
    $("#"+btnId).css('left',value);
    $("#"+progId).width(value);
};

/*
 * 匹配对应的语音消息并且快进
 */
//当前语音消息下标
var currtIndex = 0;
function findCurrentVoice(arry){
    for(var i =0;i<arry.length;i++){
        var num = Number(arry[i].endLength);
        if(num >= currentVoiceLeg){
            startSeconds = Number(currentVoiceLeg - arry[i].startLength);
            var src = arry[i].fileUrl; 
            currtIndex =i;
            playAudio(startSeconds,currtIndex,arry);
            break;
        };
    };
};

/*
 * 播放动画
 * btnId 动画按钮id
 * progId 进度条动画id
 */
function setIntVoice(btnId,progId){
    if(!isPlay){
        progress = setInterval(function(){
            moveleft+=secondNum;
            currentVoiceLeg+=1;
            voicePosition = moveleft/width*100;
            if(currentVoiceLeg>=voiceLength&&isOver){
                voicePosition = 0;  
                moveleft = 0;
                currentVoiceLeg =0;
                controlAudio();
           };  
           $(".voice-currtTime").text(getVoiceTime(currentVoiceLeg));
            setProgressBar(btnId,progId,voicePosition+"%");
        },1000);    
    }
    
};

/*
 * 播放声音函数
 *  @param  string src 播放地址
 *  @param  string num 播放声音的位置(秒)
 */
//当前语音下标
 var currntNum;
function playAudio(num,index,arry){
    isOver = false;
    var src = arry[index].fileUrl;
    currut_audio.src = src;
    currut_audio.load();
    currntNum = index;
    var audioState = setInterval(function(){
        if(currut_audio.readyState>1){
            currut_audio.currentTime = num;    
            currut_audio.play();
            controlAudio();
            clearInterval(audioState);
        };
    },100)
    currut_audio.addEventListener("error", function (e) {
        alert('亲,网络有延迟,请稍后重试!');
        
    });  
};
//监听语音播放事件
currut_audio.addEventListener("play", function () {
        isPlay = true;
}, false);
//暂停
currut_audio.addEventListener("pause", function () {
        isPlay = false;

}, false);
//停止
 currut_audio.addEventListener("ended", function () {
         startSeconds = 0;
        //播放完成后切换音频
        currtIndex+=1;
        if(currtIndex<=result.length-1){
            currentVoiceLeg = result[currtIndex].startLength;
            playAudio(startSeconds,currtIndex,result);
        }else{
            startSeconds = 0;
            currtIndex = 0;
            isOver = true;
            isPlay = false;
        }
        
}, false);
//控制界面交互,定时器
function controlAudio(){
    clearInterval(progress);
    if(!isPlay&&!isOver){
        $(".on-log").addClass("btn-pause").removeClass("btn-play").attr("data-log","暂停");
        if(currentVoiceLeg != 0){
            currut_audio.play();
        };
        setIntVoice("slider","playing-bar");
    }else{
        $(".on-log").addClass("btn-play").removeClass("btn-pause").attr("data-log","播放");
        clearInterval(progress);
        currut_audio.pause();
    }
};
//点击播放按钮
$(".on-log").on("click",function(){
    if(isOver){
        isOver = false;
        playAudio(startSeconds,currtIndex,result);
    }else{
         controlAudio();    
    }
});

代码稍微有点乱,主要是供大家参考一下实现的思想,如果哪里不足也请大家多多指教!

需要注意的地方:

  1. ios给audio设置currentTime需要等待语音资源加载完成,查了很多资料发现只有设置定时器判断audio 的readyState方案兼容性相较好;
  2. audio在微信浏览器与ios或者一些安卓浏览器不会自动播放,涉及到流量问题,微信浏览器调用wx.ready方法达到自动播放效果;
  3. 连续播放时,播放完当前播放下一条时会有一定资源加载时间,导致最后时间会多出几秒。解决方案:当播放下一条语音时获取之前语音累计所加时间,重新设置定时器开始时间;

H5有些方法兼容性还是有待提高!!!