1. 上一篇语音播报其实是不完美的,就是如何停止上一个音频开始下一个音频的问题,我在此做一下修改
比如说:现在正在播放1,我点击2让2开始播放,1停止播放,我上面的写法是有问题的:
- 通过 innerAudioContext.pause() 是可以停止播放音频的;
- wx.createInnerAudioContext()的实例,我是在readStart方法里面创建的
- 再次执行readStart方法,会重新创建wx.createInnerAudioContext()实例
- 我们如果在点击播放图标的时候执行pause() 方法,执行的不是正在播放的音频的实例
因此:我们需要创建一个全局的实例,这样我们执行pause() 方法时,在同一个实例下,就是停止正在播放的音频
代码如下:
data:{
dialogue:'您好,我是您的健康管家"向我提问。您好,我是您的健康管家"质迹"!您有什么疑问都可以在此向我提问。',
type:'play'
}
// 在onLoad中创建一个全局的实例
onLoad(){
this.innerAudioContext = wx.createInnerAudioContext();
}
// 阅读文字
readText: async function () {
const that = this;
// ------------------------------在点击播放图标之前,我们先执行一个pause()方法,停止上一段音频--------------------
that.innerAudioContext.pause()
that.setData({
type :'pause'
})
// 将文字按照每200个长度截取成一段,组成一个数组
// 切片处理,将超过1000个字符的文字,截取成几段
let list =this.splitStringByLength(dialogue, 200)
// 循环遍历数组,将文字转化成音频
let radioList = list.map(el => {
return new Promise(resolve => {
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: el,
success: function (res) {
resolve(res.filename)
},
fail: function (res) {
wx.showToast({
title: '语音转换失败',
})
}
})
})
})
// 将promise执行结果放在一起执行(解决for循环中有异步操作的方法之一,经典面试题)
// 有个弊端: 如果前面的promise有一个执行错误,Promise.all就不会执行,因此:可以用map/filter将radioList过滤一遍(本项目中有一个Promise执行失败,就应该失败)
Promise.all(radioList).then(res => {
that.readStart(res)
})
},
// 将字符串每隔length个就截取一段
splitStringByLength :function (str, length) {
let result = [];
for (let i = 0; i < str.length; i += length) {
result.push(str.substring(i, i + length));
}
return result;
},
// 开始阅读
readStart: async function (radioList) {
const that = this
for (let text of radioList) {
// --------------------------在此就不需要再次创建实例了---------------------------
// const innerAudioContext = wx.createInnerAudioContext();
that.innerAudioContext.src = text;
that.innerAudioContext.onPlay(() => {
console.log('开始播放当前片段', 'onPlay');
});
that.innerAudioContext.onError((err) => {
console.error('音频播放出错', err);
});
that.innerAudioContext.onEnded(async () => {
// 如果是最后一个片段,这里可以结束,否则不需要await
if (text === radioList[radioList.length - 1]) {
that.setData({
type: 'play',
})
}
});
// 确保前一个音频播放结束后再播放下一个
await new Promise(resolve => {
that.innerAudioContext.onEnded(resolve);
that.innerAudioContext.play();
});
}
},
// 暂停阅读
readPause: function () {
this.innerAudioContext.pause()
const that = this;
that.setData({
type: 'play',
})
}
2. 对于splitStringByLength方法,我也进行了一些优化 (wx.createInnerAudioContext()只支持1000个字符以内的文字转为音频)
下面一段代码是无脑按照长度切割,在进行语音播报的时候,第一段语音和第二段语音之前的衔接会有1-2秒的停顿,语音播报不流畅
比如:您好,我是您的健康管家"向我提问。您好,我是您的健康 | 管家"质迹"!您有什么疑问都可以在此向我提问。
在 | 处切成两段,语音在播放到‘康’的时候,会停顿1-2秒,在开始播放‘管家…’
splitStringByLength :function (str, length) {
let result = [];
for (let i = 0; i < str.length; i += length) {
result.push(str.substring(i, i + length));
}
return result;
},
在此,我对切割方法进行了一下优化
- 不超过length的长度,不进行切割
- 超过length的长度,首先找中文句号,在中文句号处切割
- 如果整个文本都没有中文句号,则在中文逗号处切割
- 语音在逗号和句号处本来就是会停顿的,在此处切割,这样用户的体验更好一些
const splitStringByLength = (text, length) => {
const MAX_LENGTH = length; // 最大长度
const CHINESE_PERIOD = '。'; // 首个切割条件
const CHINESE_COMMA = ','; // 当首个切割条件不存在时的次要切割条件
let result = [];
let startIndex = 0;
// 文本长度不超过最大长度,就不切割,直接返回
if (text.length < MAX_LENGTH) {
return [text]
}
while (startIndex < text.length) {
// 查找下一个中文句号的位置,若找不到则返回-1
let periodIndex = text.indexOf(CHINESE_PERIOD, startIndex);
if (periodIndex === -1 || periodIndex - startIndex > MAX_LENGTH) {
// 如果没有找到句号,或者句号距离起始点超过了300个字符
// 则查找中文逗号的位置,同样,若找不到则返回-1
let commaIndex = text.lastIndexOf(CHINESE_COMMA, startIndex + MAX_LENGTH);
if (commaIndex === -1 || commaIndex <= startIndex) {
// 如果也没有找到逗号,或者逗号位置不满足条件,则直接在MAX_LENGTH处截断
result.push(text.substring(startIndex, startIndex + MAX_LENGTH));
startIndex += MAX_LENGTH;
} else {
// 找到了逗号并且位置合适,就在逗号后分割
result.push(text.substring(startIndex, commaIndex + 1));
startIndex = commaIndex + 1;
}
} else {
// 找到了句号并且位置合适,就在句号后分割
result.push(text.substring(startIndex, periodIndex + 1));
startIndex = periodIndex + 1;
}
}
return result;
}