虽然RSS的内容已经是聚合精简过的,但有时还是不方便,比如开车、睡前等特殊场景下如果有语音播报,是不是更加完美呢?说干就干。
几个大厂比较了下,Goolge有网络门槛,OpenAI 免费额度太低,最终还是选了
Azure AI 的Speech Studio
每月额度50万字符
API 服务,这里的路径的坑要注意区分大小写,否则前端会报一个误导性提示(Failed to load because no supported source was found.),而导致无法播放。
public async Task<IActionResult> playArticle(FeedItem textToRead)
{
IActionResult response = Unauthorized();
try
{
var fileName = $"{textToRead.Id}.mp3";
var filePath = Path.Combine("/app/Audio", fileName);
if (System.IO.File.Exists(filePath))
{
var audioUrl = $"/Audio/{fileName}";
return Ok(new { url = audioUrl });
}
string speechKey = _configuration["MSTTS:SpeechKey"];
string speechRegion = _configuration["MSTTS:SpeechRegion"];
var speechConfig = SpeechConfig.FromSubscription(speechKey, speechRegion);
speechConfig.SpeechSynthesisVoiceName = "zh-CN-XiaoxiaoNeural"; // 选择中文语音
speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3);
using (var speechSynthesizer = new SpeechSynthesizer(speechConfig))
{
var speechSynthesisResult = await speechSynthesizer.SpeakTextAsync(textToRead.Title+ textToRead.Excerpt);
if (speechSynthesisResult.Reason == ResultReason.SynthesizingAudioCompleted)
{
await System.IO.File.WriteAllBytesAsync(filePath, speechSynthesisResult.AudioData);
var audioUrl = $"/Audio/{fileName}";
return Ok(new { url = audioUrl });
}
else
{
return BadRequest($"Speech synthesis failed. Reason: {speechSynthesisResult.Reason}");
}
}
}
catch (Exception ex)
{
return BadRequest($"Speech synthesis failed. Reason: {ex.Message}");
}
}
Nginx ,增加路径映射
location /Audio/ {
alias /app/Audio/;
add_header 'Access-Control-Allow-Origin' '*';
autoindex on;
}
前端调用时把文章传过来,就可以返回自动播放的路径。
async playArticle(article) {
const token = localStorage.getItem("userToken")
if (!token) {
this.$router.push('/login');
return;
}
try {
const response = await apiService.playArticle(article, this.setLoading,token);
const audioFilePath = response.url;
this.$refs.audioPlayer.src = audioFilePath;
this.$refs.audioPlayer.play();
} catch (error) {
console.error("播放音频时发生错误:", error);
}
},