uniapp 文本转语音
- 基于 Minimax API 的 UniApp 文本转语音工具,支持文本分段、队列播放、暂停恢复等功能。
- 目前只内置了 Minimax文本转语音
- Minimax的语音生成技术以其自然、情感丰富和实时性强而著称
API_KEY、GroupId获取方法
https://platform.minimaxi.com/user-center/basic-information/interface-key
NPM地址
特性
- 🎯 自动文本分段处理
- 🔄 队列播放管理
- ⏯️ 支持暂停/恢复
- 📦 轻量级封装
- 🎨 完整的事件系统
- 💫 支持长文本处理
安装
npm install uniapp-text-to-speech
基础使用
import SpeechSynthesisUtil from 'uniapp-text-to-speech';
const tts = new SpeechSynthesisUtil({
API_KEY: 'your_minimax_api_key',
GroupId: 'your_group_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);
}
高级功能
1. 事件监听
import { EventType } from 'uniapp-text-to-speech';
tts.on(EventType.SYNTHESIS_START, ({ text }) => {
console.log('开始合成文本:', text);
});
tts.on(EventType.AUDIO_PLAY, ({ currentText, remainingCount }) => {
console.log('正在播放:', currentText);
console.log('剩余数量:', remainingCount);
});
tts.on(EventType.AUDIO_END, ({ finishedText }) => {
console.log('播放完成:', finishedText);
});
tts.on(EventType.ERROR, ({ error }) => {
console.error('发生错误:', error);
});
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 | 开始播放 | { currentText: string, remainingCount: number } |
AUDIO_END | 播放结束 | { finishedText: string } |
PAUSE | 暂停播放 | - |
RESUME | 恢复播放 | - |
ERROR | 发生错误 | { error: Error } |
主要方法
方法名 | 说明 | 参数 | 返回值 |
---|
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 |
注意事项
- 需要先在 Minimax 申请 API_KEY 和 GroupId
- 文本会自动按标点符号分段处理,支持的标点符号优先级:
- 音频队列最大长度默认为3,可以通过构造函数参数修改
- 播放失败时会自动重试,如需手动触发可调用 manualPlay 方法
完整的示例代码
<template>
<div class="speech-container">
<textarea
v-model="inputText"
placeholder="请输入要转换的文本"
:disabled="isProcessing"
></textarea>
<div class="control-panel">
<button
@click="startPlayback"
:disabled="isProcessing || !inputText"
>
{{ isProcessing ? '处理中...' : '开始播放' }}
</button>
<button
@click="handlePlayPause"
:disabled="!canTogglePlay"
>
{{ isPaused ? '继续' : '暂停' }}
</button>
<button
@click="handleStop"
:disabled="!isPlaying && !isPaused"
>
停止
</button>
</div>
<div class="status-panel">
<div class="status-item">
<span>状态:</span>
<span :class="statusClass">{{ status }}</span>
</div>
<div class="status-item">
<span>进度:</span>
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: `${progress}%` }"
></div>
<span>{{ progress }}%</span>
</div>
</div>
</div>
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import SpeechSynthesisUtil, { EventType } from 'uniapp-text-to-speech';
const inputText = ref('');
const isProcessing = ref(false);
const isPaused = ref(false);
const isPlaying = ref(false);
const status = ref('就绪');
const progress = ref(0);
const errorMessage = ref('');
const canTogglePlay = computed(() => {
return isPlaying.value || isPaused.value;
});
const statusClass = computed(() => {
return {
'status-ready': status.value === '就绪',
'status-playing': status.value === '播放中',
'status-paused': status.value === '已暂停',
'status-error': status.value.includes('错误')
};
});
const speechUtil = new SpeechSynthesisUtil({
API_KEY: 'your_api_key',
GroupId: 'your_group_id'
});
const setupEventListeners = () => {
speechUtil.on(EventType.STATE_CHANGE, ({ newState }) => {
isPaused.value = newState.isPaused;
isPlaying.value = newState.isPlaying;
status.value = newState.isPlaying ? '播放中' :
newState.isPaused ? '已暂停' :
newState.isError ? '错误' : '就绪';
});
speechUtil.on(EventType.PROGRESS, ({ currentIndex, totalChunks }) => {
progress.value = Math.round(((currentIndex + 1) / totalChunks) * 100);
});
speechUtil.on(EventType.ERROR, ({ error }) => {
errorMessage.value = error.message;
status.value = '错误';
});
speechUtil.on(EventType.AUDIO_END, () => {
if (progress.value === 100) {
resetState();
}
});
};
const startPlayback = async () => {
if (!inputText.value) return;
isProcessing.value = true;
errorMessage.value = '';
try {
await speechUtil.processText(inputText.value);
await speechUtil.flushRemainingText();
} catch (error) {
errorMessage.value = `处理失败: ${error.message}`;
} finally {
isProcessing.value = false;
}
};
const handlePlayPause = () => {
speechUtil.togglePlay();
};
const handleStop = () => {
speechUtil.reset();
resetState();
};
const resetState = () => {
isProcessing.value = false;
isPaused.value = false;
isPlaying.value = false;
status.value = '就绪';
progress.value = 0;
errorMessage.value = '';
};
onMounted(() => {
setupEventListeners();
});
onBeforeUnmount(() => {
speechUtil.reset();
});
</script>
<style scoped>
.speech-container {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
textarea {
width: 100%;
height: 150px;
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
}
.control-panel {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background-color: #4CAF50;
color: white;
cursor: pointer;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.status-panel {
background-color: #f5f5f5;
padding: 15px;
border-radius: 4px;
}
.status-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.progress-bar {
flex: 1;
height: 20px;
background-color: #ddd;
border-radius: 10px;
overflow: hidden;
margin-left: 10px;
position: relative;
}
.progress-fill {
height: 100%;
background-color: #4CAF50;
transition: width 0.3s ease;
}
.error-message {
margin-top: 20px;
padding: 10px;
background-color: #ffebee;
color: #c62828;
border-radius: 4px;
}
.status-ready { color: #2196F3; }
.status-playing { color: #4CAF50; }
.status-paused { color: #FF9800; }
.status-error { color: #F44336; }
</style>
许可证
MIT
作者
乔振 qiaozhenleve@gmail.com