微信小程序 SpeechSynthesizer基本使用方法
微信小程序 SpeechSynthesizer 實戰(zhàn)指南
一、引言
在移動應用開發(fā)中,文本轉(zhuǎn)語音(TTS)功能可以極大地提升用戶體驗,尤其是在需要解放雙手的場景下。微信小程序提供了強大的 SpeechSynthesizer API,讓開發(fā)者可以輕松實現(xiàn)高質(zhì)量的語音合成功能。本文將結(jié)合實際項目,詳細介紹微信小程序 SpeechSynthesizer 的使用方法和最佳實踐。
二、SpeechSynthesizer 簡介
SpeechSynthesizer 是微信小程序提供的文本轉(zhuǎn)語音 API,它基于騰訊云的語音合成技術(shù),可以將文本轉(zhuǎn)換為自然流暢的語音。該 API 支持多種語言、音色和語速調(diào)節(jié),滿足不同場景的需求。
主要特點
- 高質(zhì)量語音合成 :基于騰訊云的先進語音合成技術(shù),提供自然流暢的語音輸出。
- 多語言支持 :支持中文、英文等多種語言。
- 豐富的音色選擇 :提供多種音色供選擇,包括男聲、女聲等。
- 靈活的參數(shù)調(diào)節(jié) :可以調(diào)節(jié)語速、音量、音調(diào)等參數(shù)。
- 實時合成 :支持實時將文本轉(zhuǎn)換為語音,無需等待。
三、基本使用方法
1. 創(chuàng)建 SpeechSynthesizer 實例
// 初始化語音合成實例
this.ttsInstance = new SpeechSynthesizer({
volume: 1.0, // 音量,范圍 0-1
rate: 1.0, // 語速,范圍 0.5-2.0
pitch: 1.0, // 音調(diào),范圍 0.5-2.0
language: 'zh-CN', // 語言,支持 'zh-CN'、'en-US' 等
voiceName: 'xiaoyan' // 音色,支持 'xiaoyan'、'xiaoyu' 等
});
2. 合成并播放語音
// 合成并播放語音
this.ttsInstance.speak({
text: '歡迎使用微信小程序 SpeechSynthesizer',
success: () => {
console.log('語音播放成功');
},
fail: (err) => {
console.error('語音播放失敗', err);
}
});
3. 暫停和繼續(xù)播放
// 暫停播放 this.ttsInstance.pause(); // 繼續(xù)播放 this.ttsInstance.resume();
4. 停止播放
// 停止播放 this.ttsInstance.stop();
四、高級應用
1. 實時語音合成
在聊天應用中,我們可以實時將用戶輸入的文本轉(zhuǎn)換為語音:
// 監(jiān)聽用戶輸入
onInputChange(e) {
const text = e.detail.value;
// 實時合成語音
this.ttsInstance.speak({
text: text,
success: () => {
console.log('語音合成成功');
}
});
}
2. 多段文本合成
在需要合成多段文本時,可以使用 queue 方法:
// 合成多段文本
this.ttsInstance.queue([
{ text: '第一段文本' },
{ text: '第二段文本' },
{ text: '第三段文本' }
]);
3. 自定義音色和語速
根據(jù)不同的場景,我們可以自定義音色和語速:
// 設(shè)置音色為男聲
this.ttsInstance.setVoiceName('xiaoyu');
// 設(shè)置語速為慢速
this.ttsInstance.setRate(0.7);
// 設(shè)置音量為最大
this.ttsInstance.setVolume(1.0);五、常見問題及解決方案
1. 語音合成失敗
問題描述 :調(diào)用 speak 方法時,返回失敗。
解決方案 :
- 檢查網(wǎng)絡(luò)連接是否正常。
- 檢查文本內(nèi)容是否過長(建議不超過 500 字)。
- 檢查參數(shù)設(shè)置是否正確。
2. 語音播放不流暢
問題描述 :語音播放時出現(xiàn)卡頓或斷句不自然。
解決方案 :
- 檢查網(wǎng)絡(luò)連接是否穩(wěn)定。
- 調(diào)整語速參數(shù),適當降低語速。
- 分割長文本,分段合成。
3. 音量調(diào)節(jié)無效
問題描述 :設(shè)置音量后,語音播放音量沒有變化。
解決方案 :
- 檢查音量參數(shù)是否在 0-1 范圍內(nèi)。
- 檢查設(shè)備音量是否設(shè)置正確。
六、實戰(zhàn)案例:智能語音助手
下面我們將結(jié)合實際項目,實現(xiàn)一個智能語音助手功能:
1. 初始化語音合成實例(復制可用)
// utils/ttsUtil.js
const SpeechSynthesizer = require("../../components/alibabacloud-nls-wx-sdk-master/utils/tts")
const fs = wx.getFileSystemManager();
// 格式化時間(工具函數(shù))
function formatTime(date) {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hour = date.getHours().toString().padStart(2, '0');
const minute = date.getMinutes().toString().padStart(2, '0');
const second = date.getSeconds().toString().padStart(2, '0');
return `${year}${month}${day}${hour}${minute}${second}`;
}
// 休眠(工具函數(shù))
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class TTSUtil {
constructor(config) {
this.config = config;
this.ttsInstance = null;
this.isPlaying = false;
this.currentAudioCtx = null;
this.dataInfos = { saveFile: null, saveFd: null, ttsStart: false };
this.audioTaskList = [];
this.textQueue = []; // 待合成的文本隊列
this.isProcessingQueue = false; // 是否正在處理隊列
}
// 初始化TTS(修復:增加錯誤捕獲,確保事件監(jiān)聽生效)
initTTS() {
return new Promise((resolve, reject) => {
try {
if (this.ttsInstance) {
resolve(true);
return;
}
this.ttsInstance = new SpeechSynthesizer(this.config);
console.log("[TTSUtil] 實例初始化成功");
// 監(jiān)聽音頻數(shù)據(jù)(確保二進制數(shù)據(jù)寫入文件)
this.ttsInstance.on("data", (binaryData) => {
if (this.dataInfos.saveFile && this.dataInfos.saveFd) {
try {
// 小程序中position傳-1等價于SEEK_END
fs.write({
fd: this.dataInfos.saveFd,
data: binaryData,
position: -1,
encoding: "binary",
success: () => {
console.log(`[TTSUtil] 寫入音頻數(shù)據(jù):${binaryData.byteLength}字節(jié)`);
},
fail: (e) => {
console.error("[TTSUtil] 寫入音頻數(shù)據(jù)失?。?, e);
}
});
} catch (e) {
console.error("[TTSUtil] 寫入音頻數(shù)據(jù)異常:", e);
}
}
});
this.ttsInstance.on("completed", async (res) => {
console.log("[TTSUtil] 合成完成回調(diào)觸發(fā)", res);
await sleep(800); // 延長等待,確保數(shù)據(jù)完全寫入文件
if (this.dataInfos.saveFd) {
// 改用異步close
fs.close({
fd: this.dataInfos.saveFd,
success: () => {
console.log("[TTSUtil] 文件已異步關(guān)閉");
},
fail: (err) => {
console.error("[TTSUtil] 關(guān)閉文件失?。?, err);
}
});
this.dataInfos.saveFd = null;
}
const taskItem = this.audioTaskList.find(item => item.filePath === this.dataInfos.saveFile);
if (taskItem) taskItem.status = "completed";
this.clearCurrentFileResource();
// 修復:確保任務項存在才播放
if (taskItem) {
this.playCurrentTaskAndNext(taskItem);
} else {
this.processNextQueueItem(); // 無任務項則直接處理下一個
}
});
this.ttsInstance.on("failed", (err) => {
console.error("[TTSUtil] 合成失?。?, err);
const taskItem = this.audioTaskList.find(item => item.filePath === this.dataInfos.saveFile);
if (taskItem) {
taskItem.status = "failed";
taskItem.error = err;
}
this.clearCurrentFileResource();
this.processNextQueueItem(); // 失敗后繼續(xù)處理下一個
});
resolve(true);
} catch (err) {
console.error("[TTSUtil] 初始化失?。?, err);
reject(false);
}
});
}
// 向隊列中添加文本(外部調(diào)用)
addToQueue(text, customParams = {}) {
if (!this.ttsInstance) {
uni.showToast({ title: "請先初始化TTS", icon: "none" });
return;
}
const texts = Array.isArray(text) ? text : [text];
texts.forEach(t => {
this.textQueue.push({ text: t, params: customParams });
console.log(`[TTSUtil] 文本加入隊列:${t}`);
});
// 修復:隊列未處理時,立即啟動(增加防抖,避免重復觸發(fā))
if (!this.isProcessingQueue && !this.isPlaying) {
this.processNextQueueItem();
}
}
// 處理隊列的下一個文本(修復:確保異步執(zhí)行順序)
async processNextQueueItem() {
if (this.textQueue.length === 0) {
this.isProcessingQueue = false;
console.log("[TTSUtil] 隊列已空,停止處理");
return;
}
this.isProcessingQueue = true;
const queueItem = this.textQueue.shift();
console.log(`[TTSUtil] 開始處理隊列文本:${queueItem.text}`);
// 修復:統(tǒng)一用wav格式,提升兼容性
const format = queueItem.params.format || "wav";
const task = {
id: this.audioTaskList.length + 1,
text: queueItem.text,
filePath: `${wx.env.USER_DATA_PATH}/${formatTime(new Date())}_${this.audioTaskList.length + 1}.${format}`,
status: "pending",
params: queueItem.params,
error: null
};
this.audioTaskList.push(task);
// 等待合成完成(修復:await確保合成完成后再執(zhí)行后續(xù))
await this.synthesizeSingleAudio(task, queueItem.params);
}
// 播放當前任務 + 自動處理下一個(核心修復:確保音頻播放觸發(fā))
async playCurrentTaskAndNext(taskItem) {
if (!taskItem || !taskItem.filePath) {
this.processNextQueueItem();
return;
}
console.log(`[TTSUtil] 準備播放:${taskItem.filePath}`);
// 修復:先檢查文件是否存在
try {
fs.accessSync(taskItem.filePath);
} catch (e) {
console.error("[TTSUtil] 音頻文件不存在:", e);
this.processNextQueueItem();
return;
}
// 播放當前音頻,等待播放完成后處理下一個
const playSuccess = await this.playAudioFile(taskItem.filePath);
console.log(`[TTSUtil] 當前音頻播放${playSuccess ? "完成" : "失敗"}`);
// 無論播放成功/失敗,都處理下一個隊列項
this.processNextQueueItem();
}
// 合成單個音頻(修復:等待tts.start真正完成,確保合成啟動)
synthesizeSingleAudio(task, customParams = {}) {
return new Promise((resolve) => {
// 修復:先清空當前文件資源,避免殘留
this.clearCurrentFileResource();
// 統(tǒng)一用wav格式,提升兼容性
const format = customParams.format || "wav";
fs.open({
filePath: task.filePath,
flag: "w+", // 修復:用w+替代a+,確保文件重新創(chuàng)建
success: async (res) => {
this.dataInfos.saveFd = res.fd;
this.dataInfos.saveFile = task.filePath;
console.log(`[TTSUtil] 打開文件成功:${task.filePath}`);
const playParams = {
text: task.text,
voice: customParams.voice || "zhistella",
format: format, // 強制用wav
sample_rate: customParams.sampleRate || 16000,
volume: customParams.volume || 100,
speech_rate: customParams.speechRate || 0,
pitch_rate: customParams.pitchRate || 0,
enable_subtitle: false
};
try {
// 修復:await確保start執(zhí)行完成
await this.ttsInstance.start(playParams);
task.status = "synthesizing";
console.log(`[TTSUtil] 開始合成文本:${task.text}`);
// 不立即resolve,等待合成完成(由completed/failed回調(diào)處理)
// 這里resolve僅標記合成啟動,不影響后續(xù)流程
resolve(true);
} catch (e) {
console.error("[TTSUtil] 合成啟動失?。?, e);
task.status = "failed";
task.error = e;
this.clearCurrentFileResource();
resolve(false);
}
},
fail: (err) => {
console.error(`[TTSUtil] 打開文件失?。?{err.errMsg}`);
task.status = "failed";
task.error = err;
resolve(false);
}
});
});
}
// 播放單個音頻(核心修復:確保自動播放生效)
playAudioFile(filePath) {
return new Promise((resolve) => {
// 停止當前播放的音頻
this.stopCurrentAudio();
// 增加文件路徑空值校驗
if (!filePath) {
console.error("[TTSUtil] 音頻路徑為空");
resolve(false);
return;
}
// 創(chuàng)建音頻上下文
this.currentAudioCtx = wx.createInnerAudioContext();
if (!this.currentAudioCtx) {
console.error("[TTSUtil] 音頻上下文創(chuàng)建失敗");
resolve(false);
return;
}
this.currentAudioCtx.src = filePath;
this.isPlaying = true;
// 監(jiān)聽音頻加載完成
this.currentAudioCtx.onCanplay(() => {
console.log(`[TTSUtil] 音頻加載完成,開始播放:${filePath}`);
// 直接調(diào)用play(同步方法,無返回值),通過onError捕獲錯誤
this.currentAudioCtx.play();
});
this.currentAudioCtx.onPlay(() => {
console.log(`[TTSUtil] 音頻開始播放:${filePath}`);
});
// 用onError替代catch捕獲播放錯誤
this.currentAudioCtx.onError((err) => {
console.error("[TTSUtil] 播放錯誤:", err);
this.isPlaying = false;
this.deleteFile(filePath);
resolve(false);
});
this.currentAudioCtx.onEnded(() => {
console.log(`[TTSUtil] 音頻播放完成:${filePath}`);
this.isPlaying = false;
this.deleteFile(filePath);
this.audioTaskList = this.audioTaskList.filter(item => item.filePath !== filePath);
resolve(true);
});
// 超時兜底:5秒未播放則判定失敗
setTimeout(() => {
if (this.isPlaying && !this.currentAudioCtx?.paused) return;
console.error(`[TTSUtil] 音頻播放超時:${filePath}`);
this.isPlaying = false;
this.deleteFile(filePath);
resolve(false);
}, 5000);
});
}
// 停止當前音頻(保持不變)
stopCurrentAudio() {
if (this.currentAudioCtx) {
try {
this.currentAudioCtx.stop();
this.currentAudioCtx.destroy();
} catch (e) { }
this.currentAudioCtx = null;
}
this.isPlaying = false;
}
// 停止所有(保持不變)
stopAll() {
this.stopCurrentAudio();
if (this.ttsInstance) {
this.ttsInstance.shutdown();
}
this.clearCurrentFileResource();
this.textQueue = [];
this.isProcessingQueue = false;
console.log("[TTSUtil] 已停止所有合成/播放,清空隊列");
}
// 清理文件資源(保持不變)
clearCurrentFileResource() {
if (this.dataInfos.saveFd) {
// 改用異步close
fs.close({
fd: this.dataInfos.saveFd,
success: () => {
console.log("[TTSUtil] 文件句柄已關(guān)閉");
},
fail: (e) => {
console.error("[TTSUtil] 關(guān)閉文件句柄失敗:", e);
}
});
this.dataInfos.saveFd = null;
}
this.dataInfos.saveFile = null;
this.dataInfos.ttsStart = false;
}
// 刪除文件(保持不變)
deleteFile(filePath) {
try {
fs.unlinkSync(filePath);
console.log(`[TTSUtil] 刪除文件:${filePath}`);
} catch (e) {
console.error(`[TTSUtil] 刪除文件失?。?{e}`);
}
}
// 銷毀資源(保持不變)
destroy() {
this.stopAll();
this.audioTaskList.forEach(task => {
if (task.filePath) this.deleteFile(task.filePath);
});
this.audioTaskList = [];
this.ttsInstance = null;
}
// 新增:獲取隊列長度(方便調(diào)試)
getQueueLength() {
return this.textQueue.length;
}
}
// 單例導出
let ttsInstance = null;
export function getTTSUtil(config) {
if (!ttsInstance) {
ttsInstance = new TTSUtil(config);
}
return ttsInstance;
}
export default TTSUtil;2. 在頁面中使用
// index.vue
import ttsAudio from './ttsAudio.js';
export default {
data() {
return {
inputText: '',
ttsConfig: {
appkey: "xxxxxxxxxx", // 你的appkey
token: "你的阿里云語音合成token", // 你的token(需通過appkey+secret換取)直接讓后臺寫接口返回
url: "wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1" // 阿里云語音合成服務地址
},
};
},
methods: {
// 播放語音
// 初始化TTS實例
async initTTS() {
this.ttsUtil = getTTSUtil(this.ttsConfig);
await this.ttsUtil.initTTS();
// 調(diào)用
let text = '一只穿著京劇戲服的可愛的灰白臨清獅貓,擬人化,現(xiàn)實風格,瘋狂的細節(jié),毛茸茸的,超清晰的毛發(fā),大眼睛,全身照,在戲臺上耍著戲曲里的花槍,擺著動作表演,對著鏡頭,Q版超萌,超高清,杰作'
this.ttsUtil.addToQueue(text, {
voice: "zhistella",
speechRate: 0
});
// res ? uni.showToast({ title: "TTS初始化成功", icon: "success" }) : uni.showToast({ title: "初始化失敗", icon: "none" });
},
// 暫停播放
pauseVoice() {
ttsAudio.pause();
},
// 繼續(xù)播放
resumeVoice() {
ttsAudio.resume();
},
// 停止播放
stopVoice() {
ttsAudio.stop();
}
}
};七、總結(jié)
微信小程序 SpeechSynthesizer 是一個功能強大的文本轉(zhuǎn)語音 API,它可以幫助開發(fā)者輕松實現(xiàn)高質(zhì)量的語音合成功能。通過本文的介紹,相信你已經(jīng)掌握了 SpeechSynthesizer 的基本使用方法和高級應用技巧。在實際項目中,你可以根據(jù)需求靈活運用這些技巧,為用戶提供更好的體驗。
八、參考資料
到此這篇關(guān)于微信小程序 SpeechSynthesizer基本使用方法的文章就介紹到這了,更多相關(guān)微信小程序 SpeechSynthesizer使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
兼容ie和firefox的鼠標經(jīng)過(onmouseover和onmouseout)實現(xiàn)--簡短版
兼容ie和firefox的鼠標經(jīng)過(onmouseover和onmouseout)實現(xiàn)--簡短版...2007-11-11
JavaScript實現(xiàn)拖拽網(wǎng)頁內(nèi)元素的方法
這篇文章主要介紹了JavaScript實現(xiàn)拖拽網(wǎng)頁內(nèi)元素的方法,以注釋形式較為詳細的分析了javascript事件監(jiān)聽、元素定位的相關(guān)技巧,并配有詳細的注釋以便于理解,需要的朋友可以參考下2015-04-04
在IE中調(diào)用javascript打開Excel的代碼(downmoon原作)
在IE中調(diào)用javascript打開Excel的代碼(downmoon原作)...2007-04-04
Handtrack.js庫實現(xiàn)實時監(jiān)測手部運動(推薦)
這篇文章主要介紹了實時監(jiān)測手部運動的 JS 庫,可以實現(xiàn)很多有趣功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
一文帶你搞懂Electron如何優(yōu)雅的進行進程間通訊
這篇文章主要為大家詳細介紹了Electron是如何優(yōu)雅的進行進程間通訊的,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-11-11

