161 lines
5.5 KiB
JavaScript
161 lines
5.5 KiB
JavaScript
|
|
// src/composables/useJitsiTranscription.ts
|
|||
|
|
import { ref } from 'vue';
|
|||
|
|
import { XunfeiTranscription } from '@/utils/xunfei';
|
|||
|
|
|
|||
|
|
export function useJitsiTranscription(meetingId: string) {
|
|||
|
|
const isRecording = ref(false);
|
|||
|
|
const currentSpeaker = ref<string | null>(null);
|
|||
|
|
let mediaRecorder: MediaRecorder | null = null;
|
|||
|
|
let xunfeiWs: XunfeiTranscription | null = null;
|
|||
|
|
|
|||
|
|
// 初始化Jitsi API
|
|||
|
|
const initJitsiApi = (domain: string, roomName: string) => {
|
|||
|
|
const api = new JitsiMeetExternalAPI(domain, {
|
|||
|
|
roomName: roomName,
|
|||
|
|
width: '100%',
|
|||
|
|
height: '100%',
|
|||
|
|
parentNode: document.querySelector('#jitsi-container'),
|
|||
|
|
userInfo: {
|
|||
|
|
displayName: '张三',
|
|||
|
|
email: 'zhangsan@example.com'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听主讲人变化(核心:识别说话人)
|
|||
|
|
api.addEventListener('dominantSpeakerChanged', (event) => {
|
|||
|
|
const speakerId = event.id;
|
|||
|
|
console.log('主讲人切换:', speakerId);
|
|||
|
|
|
|||
|
|
// 获取说话人信息
|
|||
|
|
api.getParticipantsInfo().then(participants => {
|
|||
|
|
const speaker = participants.find(p => p.participantId === speakerId);
|
|||
|
|
if (speaker) {
|
|||
|
|
currentSpeaker.value = speaker.displayName;
|
|||
|
|
console.log('当前说话人:', speaker.displayName);
|
|||
|
|
|
|||
|
|
// 开始录制该说话人的音频
|
|||
|
|
startRecording(speakerId, speaker.displayName);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听音频轨道添加
|
|||
|
|
api.addEventListener('trackAdded', (event) => {
|
|||
|
|
if (event.track.getType() === 'audio') {
|
|||
|
|
console.log('音频轨道添加:', event.track.getParticipantId());
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return api;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 开始录制音频
|
|||
|
|
const startRecording = async (speakerId: string, speakerName: string) => {
|
|||
|
|
try {
|
|||
|
|
// 获取会议音频流
|
|||
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|||
|
|
|
|||
|
|
mediaRecorder = new MediaRecorder(stream, {
|
|||
|
|
mimeType: 'audio/webm'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let audioChunks: Blob[] = [];
|
|||
|
|
|
|||
|
|
mediaRecorder.ondataavailable = (event) => {
|
|||
|
|
if (event.data.size > 0) {
|
|||
|
|
audioChunks.push(event.data);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 每3秒发送一次音频数据进行转写
|
|||
|
|
mediaRecorder.onstop = async () => {
|
|||
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
|||
|
|
|
|||
|
|
// 发送到讯飞/阿里云进行转写
|
|||
|
|
const transcription = await transcribeAudio(audioBlob, speakerId, speakerName);
|
|||
|
|
|
|||
|
|
// 保存转录结果
|
|||
|
|
await saveTranscription(meetingId, {
|
|||
|
|
speakerId: speakerId,
|
|||
|
|
speakerName: speakerName,
|
|||
|
|
content: transcription.text,
|
|||
|
|
confidence: transcription.confidence,
|
|||
|
|
startTime: new Date().toISOString(),
|
|||
|
|
endTime: new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
audioChunks = [];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
mediaRecorder.start();
|
|||
|
|
isRecording.value = true;
|
|||
|
|
|
|||
|
|
// 每3秒停止并重新开始,实现分段录制
|
|||
|
|
setInterval(() => {
|
|||
|
|
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
|||
|
|
mediaRecorder.stop();
|
|||
|
|
setTimeout(() => mediaRecorder?.start(), 100);
|
|||
|
|
}
|
|||
|
|
}, 3000);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('录制失败:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 使用讯飞实时转写
|
|||
|
|
const transcribeAudio = async (audioBlob: Blob, speakerId: string, speakerName: string) => {
|
|||
|
|
// 连接讯飞WebSocket
|
|||
|
|
if (!xunfeiWs) {
|
|||
|
|
xunfeiWs = new XunfeiTranscription({
|
|||
|
|
appId: 'YOUR_APP_ID',
|
|||
|
|
apiKey: 'YOUR_API_KEY',
|
|||
|
|
onResult: (result) => {
|
|||
|
|
console.log('实时转写结果:', result);
|
|||
|
|
|
|||
|
|
// 实时保存到数据库
|
|||
|
|
saveTranscription(meetingId, {
|
|||
|
|
speakerId: speakerId,
|
|||
|
|
speakerName: speakerName,
|
|||
|
|
content: result.text,
|
|||
|
|
confidence: result.confidence,
|
|||
|
|
startTime: result.startTime,
|
|||
|
|
endTime: result.endTime,
|
|||
|
|
isFinal: result.isFinal
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送音频数据
|
|||
|
|
const arrayBuffer = await audioBlob.arrayBuffer();
|
|||
|
|
xunfeiWs.send(arrayBuffer);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
text: '转写结果(异步返回)',
|
|||
|
|
confidence: 0.95
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存转录结果到后端
|
|||
|
|
const saveTranscription = async (meetingId: string, data: any) => {
|
|||
|
|
try {
|
|||
|
|
await fetch('/api/workcase/meeting/transcription/save', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
meetingId: meetingId,
|
|||
|
|
...data
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('保存转录失败:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
initJitsiApi,
|
|||
|
|
isRecording,
|
|||
|
|
currentSpeaker
|
|||
|
|
};
|
|||
|
|
}
|