esp-ai
Version:
Provide a complete set of AI dialogue solutions for your development board, including but not limited to the IAT+LLM+TTS integration solution for the ESP32 series development board. | 为你的开发板提供全套的AI对话方案,包括但不限于 `ESP32` 系列开发板的 `IAT+LLM+TTS` 集成方案。
168 lines (151 loc) • 5.79 kB
JavaScript
/**
* Copyright (c) 2024 小明IO
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Commercial use of this software requires prior written authorization from the Licensor.
* 请注意:将 ESP-AI 代码用于商业用途需要事先获得许可方的授权。
* 删除与修改版权属于侵权行为,请尊重作者版权,避免产生不必要的纠纷。
*
* @author 小明IO
* @email 1746809408@qq.com
* @github https://github.com/wangzongming/esp-ai
* @websit https://espai.fun
*/
const https = require('https');
const http = require('http');
const log = require('../utils/log');
const { PassThrough } = require('stream');
const Audio_sender = require("../utils/audio_sender");
function isHttpUrl(url) {
const regex = /^https?:\/\/.+/;
return regex.test(url);
}
function isHttpsUrl(url) {
const regex = /^https:\/\/.+/;
return regex.test(url);
}
function urlToStream(url, errCb, onEnd) {
try {
const stream = new PassThrough();
const hf = isHttpsUrl(url) ? https : http;
hf.get(url, {
timeout: 100000,
}, (response) => {
if (response.statusCode !== 200) {
log.error(`音频地址不可用:${url}`)
stream.emit('error', new Error(`Request failed with status code ${response.statusCode}`));
return;
}
response.pipe(stream);
}).on('error', (err) => {
errCb && errCb();
log.error(`音频播放错误:${err}`)
stream.emit('error', err);
}).on("close", () => {
log.t_info('音频下载完毕');
onEnd && onEnd();
});
return stream;
} catch (err) {
console.log(err)
log.error(`音频播放错误: ${err}`)
errCb && errCb();
}
}
/**
* 播放任何 mp3、wav 的声音 (<=128波特率)
* 提供 http 地址
*/
function play_audio(url, client_ws, task_id, session_id, device_id, seek, on_end) {
let send_end_buffer_timer = null;
try {
if (!isHttpUrl(url)) {
log.error("play_audio 不支持本地地址!")
return;
}
if (!G_devices.get(device_id)) return;
let start_audio_time = null;
const audio_sender = new Audio_sender(client_ws, device_id);
audio_sender.startSend(session_id);
client_ws && client_ws.send(JSON.stringify({ type: "play_audio", tts_task_id: task_id }));
client_ws && client_ws.send(JSON.stringify({ type: "session_status", status: "tts_chunk_start" }));
// 结束时一定要调用本函数
let ended = false;
async function endCb(event = "play_end") {
if (!G_devices.get(device_id)) return;
if (ended) return;
ended = true;
// 真正结束
const end_time = Date.now(); // 结束时间
const play_time = end_time - start_audio_time; // 播放时间
const end_res = on_end && await on_end({
start_time: start_audio_time,
end_time: end_time,
play_time: Math.floor(play_time / 1000),
break_second: Math.floor(seek + play_time / 1000),
event,
seek
})
G_devices.set(device_id, {
...G_devices.get(device_id),
first_session: true,
play_audio_on_end: null,
})
if (end_res?.url) {
play_audio(end_res?.url, client_ws, task_id, session_id, device_id, end_res?.seek, on_end);
}
}
G_devices.set(device_id, {
...G_devices.get(device_id),
// 播放音频的参数
play_audio_on_end: (event) => {
audio_sender.sendAudio(null, G_session_ids["tts_all_end"]);
audio_sender.stop();
endCb(event);
},
play_audio_seek: seek || 0,
})
let is_parse_over = false;
const output_stream = urlToStream(url,
() => {
endCb("error");
audio_sender.sendAudio(null, G_session_ids["tts_all_end"]);
audio_sender.stop();
},
() => {
is_parse_over = true;
audio_sender.sendAudio(null, G_session_ids["tts_all_end"]);
}
);
let is_first_on_data = true;
output_stream.on('data', function (audio) {
if (is_first_on_data) {
if (!G_devices.get(device_id)) return;
is_first_on_data = false;
start_audio_time = Date.now();
}
audio.length && audio_sender.sendAudio(audio);
});
} catch (err) {
clearTimeout(send_end_buffer_timer);
if (!G_devices.get(device_id)) return;
G_devices.set(device_id, {
...G_devices.get(device_id),
play_audio_on_end: null,
})
log.error("音频播放失败:" + err);
console.error(err)
}
}
module.exports = play_audio;