uniapp 文本转语音
uniapp 文本转语音
- 基于 Minimax API 的 UniApp 文本转语音工具,支持文本分段、队列播放、暂停恢复等功能。
- 目前只内置了 Minimax文本转语音
- Minimax 的语音生成技术以其自然、情感丰富和实时性强而著称
API_KEY、GroupId 获取方法
https://platform.minimaxi.com/user-center/basic-information/interface-key
NPM 地址
特性
- 🎯 自动文本分段处理
- 🔄 队列播放管理
- ⏯️ 支持暂停/恢复
- 📦 轻量级封装
- 🎨 完整的事件系统
- 💫 支持长文本处理
安装 (推荐 npm 安装,而不是导入)
npm install uniapp-text-to-speech
基础使用
import SpeechSynthesisUtil from 'uniapp-text-to-speech';
// 初始化
const tts = new SpeechSynthesisUtil({
API_KEY: 'your_minimax_api_key', // Minimax API密钥
GroupId: 'your_group_id', // Minimax 组ID
MAX_QUEUE_LENGTH: 3, // 可选:音频队列最大长度
modelConfig: { // 可选:音频生成配置
model: 'speech-01-240228',
voice_setting: {
"voice_id": "female-tianmei",
"speed": 1,
"vol": 1,
}
},
// 其他配置...
}
});
// 基础播放
try {
await tts.textToSpeech('你好,世界!');
} catch (error) {
console.error('语音合成失败:', error);
}
分段使用
- 模拟 AI 大模型流式返回数据,自动会处理合成分段:[“你好”,“我是一个ai机器人”,“我的名字叫做阿强”]
import SpeechSynthesisUtil from "uniapp-text-to-speech";
// 初始化
const tts = new SpeechSynthesisUtil({
API_KEY: "your_minimax_api_key", // Minimax API密钥
GroupId: "your_group_id", // Minimax 组ID
});
const mockTexts = ['你好,', '我是', '人工智能助手,', '很高兴认识你!'];
try {
for (const text of mockTexts) {
await tts.processText(text);
}
await tts.flushRemainingText();
} catch (error) {
addLog(`分段播放失败: ${error.message}`);
}
高级功能
1. 事件监听
import { EventType } from "uniapp-text-to-speech";
// 监听合成开始
tts.on(EventType.SYNTHESIS_START, ({ text }) => {
console.log(`开始合成文本: ${text}`);
});
// 监听播放开始
tts.on(EventType.AUDIO_PLAY, ({ currentText }) => {
console.log(`正在播放: ${currentText}`);
status.value = "播放中";
});
// 监听播放结束
tts.on(EventType.AUDIO_END, ({ finishedText }) => {
console.log(`播放完成: ${finishedText}`);
status.value = "就绪";
progress.value = 100;
});
// 监听错误
tts.on(EventType.ERROR, ({ error }) => {
console.log(`错误: ${error.message}`);
status.value = "错误";
});
// 监听暂停
tts.on(EventType.PAUSE, () => {
console.log("播放已暂停");
status.value = "已暂停";
isPaused.value = true;
});
// 监听恢复
tts.on(EventType.RESUME, () => {
console.log("播放已恢复");
status.value = "播放中";
isPaused.value = false;
});
2. 暂停和恢复
// 暂停播放
tts.pause();
// 恢复播放
tts.resume();
// 切换播放/暂停状态
tts.togglePlay();
3. 长文本分段处理
// 自动按标点符号分段处理长文本
await tts.processText("这是第一句话。这是第二句话!这是第三句话?");
// 强制处理剩余未播放的文本
await tts.flushRemainingText();
// 重置文本处理器
tts.resetTextProcessor();
4. 状态管理
// 获取当前状态
const state = tts.getState();
console.log("是否正在播放:", state.isPlaying);
console.log("是否已暂停:", state.isPaused);
// 重置所有状态
tts.reset();
API 文档
构造函数选项
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
API_KEY | string | 是 | Minimax API 密钥 |
GroupId | string | 是 | Minimax 组 ID |
MAX_QUEUE_LENGTH | number | 否 | 音频队列最大长度,默认为 3 |
modelConfig | object | 否 | 合成语音配置,参考minimaxi |
事件类型
事件名 | 说明 | 回调参数 |
---|---|---|
SYNTHESIS_START | 开始合成 | { text: string } |
SYNTHESIS_END | 合成结束 | { text: string } |
AUDIO_PLAY | 开始播放单个音频片段 | { text: string } |
AUDIO_END | 所有音频播放完成 | { text: string } |
PAUSE | 暂停播放 | - |
RESUME | 恢复播放 | - |
ERROR | 发生错误 | { error: Error } |
事件说明
AUDIO_PLAY
: 每个音频片段开始播放时触发AUDIO_END
: 仅在所有音频片段都播放完成后触发一次
使用示例
import SpeechSynthesisUtil, { EventType } from "uniapp-text-to-speech";
const tts = new SpeechSynthesisUtil({
API_KEY: "your_minimax_api_key",
GroupId: "your_group_id",
modelConfig: {
model: "speech-01-240228",
voice_setting: {
voice_id: "female-yujie", // 默认使用悦姐声音
speed: 1.2,
vol: 1,
},
},
});
// 监听播放完成事件
tts.on(EventType.AUDIO_END, ({ text }) => {
console.log("所有音频播放完成,最后播放的文本:", text);
});
// 分段播放示例
async function playMultipleTexts() {
await tts.processText("第一段文本");
await tts.processText("第二段文本");
await tts.flushRemainingText(); // 确保所有文本都被处理
}
// 重置播放状态
tts.reset();
注意事项
- 需要先在 Minimax 申请 API_KEY 和 GroupId
- 文本会自动按标点符号分段处理,支持的标点符号优先级:
- 高优先级:。!?
- 中优先级:;:
- 低优先级:,、
- 音频队列最大长度默认为 3,可以通过构造函数参数修改
AUDIO_END
事件只会在所有音频片段播放完成后触发一次- 使用
reset()
方法可以重置所有播放状态和计数器
主要的方
方法名 | 说明 | 参数 | 返回值 |
---|---|---|---|
textToSpeech | 文本转语音 | text: string | Promise |
processText | 处理长文本 | text: string | Promise |
pause | 暂停播放 | - | void |
resume | 恢复播放 | - | void |
togglePlay | 切换播放状态 | - | void |
reset | 重置所有状态 | - | void |
on | 添加事件监听 | event: EventType, callback: Function | void |
off | 移除事件监听 | event: EventType, callback: Function | void |
完整的示例代码
<template>
<div class="speech-demo">
<!-- 基础演示区域 -->
<section class="demo-section">
<h3>基础演示</h3>
<textarea v-model="basicText" placeholder="请输入要转换的文本"></textarea>
<button @click="handleBasicSpeech">开始播放</button>
</section>
<!-- 分段演示区域 -->
<section class="demo-section">
<h3>分段播放演示</h3>
<div class="segment-container">
<div v-for="(text, index) in mockTexts" :key="index" class="segment">
<span>{{ text }}</span>
</div>
</div>
<button @click="handleSegmentSpeech">分段播放</button>
</section>
<!-- 高级功能演示区域 -->
<section class="demo-section">
<h3>高级功能演示</h3>
<div class="controls">
<button @click="handleTogglePlay">{{ isPaused ? '继续' : '暂停' }}</button>
<button @click="handleReset">重置</button>
</div>
<div class="status">
<p>当前状态: {{ status }}</p>
<p>播放进度: {{ progress }}%</p>
</div>
</section>
<!-- 事件日志区域 -->
<section class="demo-section">
<h3>事件日志</h3>
<div class="log-container">
<div v-for="(log, index) in eventLogs" :key="index" class="log-item">
{{ log }}
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import SpeechSynthesisUtil, { EventType } from 'uniapp-text-to-speech';
// 响应式状态
const basicText = ref('你好,这是一个基础示例。');
const mockTexts = ref(['你好,', '我是', '人工智能助手,', '很高兴认识你!']);
const status = ref('就绪');
const progress = ref(0);
const isPaused = ref(false);
const eventLogs = ref<string[]>([]);
// 初始化语音工具
const tts = new SpeechSynthesisUtil({
API_KEY: 'your_minimax_api_key', // Minimax API密钥
GroupId: 'your_group_id', // Minimax 组ID
modelConfig: {
model: 'speech-01-240228',
voice_setting: {
voice_id: "female-yujie",
speed: 1,
vol: 1
}
}
});
// 添加日志
const addLog = (message : string) => {
eventLogs.value.unshift(`${new Date().toLocaleTimeString()}: ${message}`);
if (eventLogs.value.length > 10) {
eventLogs.value.pop();
}
};
// 设置事件监听
const setupEventListeners = () => {
// 监听合成开始
tts.on(EventType.SYNTHESIS_START, ({ text }) => {
addLog(`开始合成文本: ${text}`);
});
// 监听播放开始
tts.on(EventType.AUDIO_PLAY, ({ currentText }) => {
addLog(`正在播放: ${currentText}`);
status.value = '播放中';
});
// 监听播放结束
tts.on(EventType.AUDIO_END, ({ finishedText }) => {
addLog(`播放完成: ${finishedText}`);
status.value = '就绪';
progress.value = 100;
});
// 监听错误
tts.on(EventType.ERROR, ({ error }) => {
addLog(`错误: ${error.message}`);
status.value = '错误';
});
// 监听暂停
tts.on(EventType.PAUSE, () => {
addLog('播放已暂停');
status.value = '已暂停';
isPaused.value = true;
});
// 监听恢复
tts.on(EventType.RESUME, () => {
addLog('播放已恢复');
status.value = '播放中';
isPaused.value = false;
});
};
// 基础播放示例
const handleBasicSpeech = async () => {
try {
await tts.textToSpeech(basicText.value);
} catch (error) {
addLog(`播放失败: ${error.message}`);
}
};
// 分段播放示例
const handleSegmentSpeech = async () => {
try {
for (const text of mockTexts.value) {
await tts.processText(text);
}
await tts.flushRemainingText();
} catch (error) {
addLog(`分段播放失败: ${error.message}`);
}
};
// 切换播放/暂停
const handleTogglePlay = () => {
tts.togglePlay();
};
// 重置播放
const handleReset = () => {
tts.reset();
status.value = '就绪';
progress.value = 0;
isPaused.value = false;
addLog('已重置所有状态');
};
// 生命周期钩子
onMounted(() => {
setupEventListeners();
});
onBeforeUnmount(() => {
tts.reset();
});
</script>
<style scoped>
.speech-demo {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.demo-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
}
textarea {
width: 100%;
height: 100px;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
}
button {
padding: 8px 16px;
margin-right: 10px;
border: none;
border-radius: 4px;
background-color: #4CAF50;
color: white;
cursor: pointer;
}
button:disabled {
background-color: #cccccc;
}
.segment-container {
margin-bottom: 15px;
}
.segment {
display: inline-block;
padding: 5px 10px;
margin: 5px;
background-color: #f5f5f5;
border-radius: 4px;
}
.controls {
margin-bottom: 15px;
}
.status {
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}
.log-container {
height: 200px;
overflow-y: auto;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
.log-item {
padding: 5px;
border-bottom: 1px solid #eee;
font-family: monospace;
}
</style>
许可证
MIT
作者
乔振 qiaozhenleve@gmail.com