133 lines
4.9 KiB
JavaScript
133 lines
4.9 KiB
JavaScript
|
|
const { Signer } = require('@volcengine/openapi');
|
|||
|
|
const fetch = require('node-fetch');
|
|||
|
|
const { AccessToken, privileges } = require('../lib/token');
|
|||
|
|
|
|||
|
|
class VolcengineService {
|
|||
|
|
constructor() {
|
|||
|
|
this.baseUrl = 'https://rtc.volcengineapi.com';
|
|||
|
|
this.service = 'rtc';
|
|||
|
|
this.region = 'cn-north-1';
|
|||
|
|
this.version = '2024-12-01';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async startVoiceChat(config) {
|
|||
|
|
console.log('[Volcengine] Starting voice chat (S2S端到端 + LLM混合, API v2024-12-01)');
|
|||
|
|
console.log('[Volcengine] RoomId:', config.RoomId);
|
|||
|
|
// ProviderParams 可能是 JSON 字符串或对象
|
|||
|
|
let pp = config.Config.S2SConfig?.ProviderParams;
|
|||
|
|
if (typeof pp === 'string') {
|
|||
|
|
try { pp = JSON.parse(pp); } catch (e) { pp = {}; }
|
|||
|
|
}
|
|||
|
|
console.log('[Volcengine] S2S AppId:', pp?.app?.appid);
|
|||
|
|
console.log('[Volcengine] S2S model:', pp?.dialog?.extra?.model);
|
|||
|
|
console.log('[Volcengine] S2S speaker:', pp?.tts?.speaker);
|
|||
|
|
console.log('[Volcengine] ProviderParams type:', typeof config.Config.S2SConfig?.ProviderParams);
|
|||
|
|
console.log('[Volcengine] LLM EndPointId:', config.Config.LLMConfig?.EndPointId);
|
|||
|
|
console.log('[Volcengine] Tools:', config.Config.LLMConfig?.Tools?.length || 0);
|
|||
|
|
console.log('[Volcengine] Full request body:', JSON.stringify(config, null, 2));
|
|||
|
|
const result = await this._callOpenAPI('StartVoiceChat', config);
|
|||
|
|
console.log('[Volcengine] StartVoiceChat response:', JSON.stringify(result, null, 2));
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async updateVoiceChat(params) {
|
|||
|
|
console.log('[Volcengine] Updating voice chat (v2024-12-01)');
|
|||
|
|
console.log('[Volcengine] UpdateVoiceChat params:', JSON.stringify(params, null, 2));
|
|||
|
|
const result = await this._callOpenAPI('UpdateVoiceChat', params);
|
|||
|
|
console.log('[Volcengine] UpdateVoiceChat response:', JSON.stringify(result, null, 2));
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async stopVoiceChat(params) {
|
|||
|
|
console.log('[Volcengine] Stopping voice chat, RoomId:', params.RoomId);
|
|||
|
|
return this._callOpenAPI('StopVoiceChat', params);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成 RTC 入房 Token
|
|||
|
|
* 使用官方 AccessToken 库:https://github.com/volcengine/rtc-aigc-demo/blob/main/Server/token.js
|
|||
|
|
*/
|
|||
|
|
generateRTCToken(roomId, userId) {
|
|||
|
|
const appId = process.env.VOLC_RTC_APP_ID;
|
|||
|
|
const appKey = process.env.VOLC_RTC_APP_KEY;
|
|||
|
|
|
|||
|
|
if (!appId || !appKey || appKey === 'your_rtc_app_key') {
|
|||
|
|
console.warn('[Volcengine] RTC AppKey not configured, returning placeholder token');
|
|||
|
|
return `placeholder_token_${roomId}_${userId}_${Date.now()}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const token = new AccessToken(appId, appKey, roomId, userId);
|
|||
|
|
const expireTime = Math.floor(Date.now() / 1000) + 24 * 3600; // 24 小时有效
|
|||
|
|
token.expireTime(expireTime);
|
|||
|
|
token.addPrivilege(privileges.PrivPublishStream, 0);
|
|||
|
|
token.addPrivilege(privileges.PrivSubscribeStream, 0);
|
|||
|
|
|
|||
|
|
const serialized = token.serialize();
|
|||
|
|
console.log(`[Volcengine] RTC Token generated for room=${roomId}, user=${userId}`);
|
|||
|
|
return serialized;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _callOpenAPI(action, body, versionOverride) {
|
|||
|
|
const ak = process.env.VOLC_ACCESS_KEY_ID;
|
|||
|
|
const sk = process.env.VOLC_SECRET_ACCESS_KEY;
|
|||
|
|
const version = versionOverride || this.version;
|
|||
|
|
|
|||
|
|
if (!ak || !sk || ak === 'your_access_key_id') {
|
|||
|
|
console.warn(`[Volcengine] Credentials not configured, returning mock response for ${action}`);
|
|||
|
|
return this._mockResponse(action, body);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 与官方 rtc-aigc-demo 完全一致的签名方式
|
|||
|
|
const openApiRequestData = {
|
|||
|
|
region: this.region,
|
|||
|
|
method: 'POST',
|
|||
|
|
params: {
|
|||
|
|
Action: action,
|
|||
|
|
Version: version,
|
|||
|
|
},
|
|||
|
|
headers: {
|
|||
|
|
Host: 'rtc.volcengineapi.com',
|
|||
|
|
'Content-type': 'application/json',
|
|||
|
|
},
|
|||
|
|
body,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const signer = new Signer(openApiRequestData, this.service);
|
|||
|
|
signer.addAuthorization({ accessKeyId: ak, secretKey: sk });
|
|||
|
|
|
|||
|
|
const url = `${this.baseUrl}?Action=${action}&Version=${version}`;
|
|||
|
|
console.log(`[Volcengine] ${action} calling:`, url);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(url, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: openApiRequestData.headers,
|
|||
|
|
body: JSON.stringify(body),
|
|||
|
|
});
|
|||
|
|
const data = await response.json();
|
|||
|
|
|
|||
|
|
if (data?.ResponseMetadata?.Error) {
|
|||
|
|
const err = data.ResponseMetadata.Error;
|
|||
|
|
throw new Error(`${action} failed: ${err.Code} - ${err.Message}`);
|
|||
|
|
}
|
|||
|
|
return data;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`[Volcengine] ${action} error:`, error.message);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Mock 响应(开发阶段凭证未配置时使用)
|
|||
|
|
*/
|
|||
|
|
_mockResponse(action, params) {
|
|||
|
|
console.log(`[Volcengine][MOCK] ${action} called with:`, JSON.stringify(params, null, 2).substring(0, 500));
|
|||
|
|
return {
|
|||
|
|
ResponseMetadata: { RequestId: `mock-${Date.now()}`, Action: action },
|
|||
|
|
Result: { Message: 'Mock response - credentials not configured' },
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = new VolcengineService();
|