194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
import { connect, Room, LocalVideoTrack, LocalAudioTrack, RemoteParticipant, LocalParticipant } from 'twilio-video';
|
|
import { twilioConfig, videoOptions, RoomType, TOKEN_SERVER_URL } from '../config/twilio';
|
|
|
|
export interface TwilioToken {
|
|
token: string;
|
|
identity: string;
|
|
roomName: string;
|
|
}
|
|
|
|
export interface VideoCallOptions {
|
|
roomName: string;
|
|
identity: string;
|
|
roomType?: RoomType;
|
|
audio?: boolean;
|
|
video?: boolean;
|
|
}
|
|
|
|
export interface ParticipantInfo {
|
|
identity: string;
|
|
sid: string;
|
|
isLocal: boolean;
|
|
audioEnabled: boolean;
|
|
videoEnabled: boolean;
|
|
}
|
|
|
|
export class TwilioService {
|
|
private room: Room | null = null;
|
|
private localVideoTrack: LocalVideoTrack | null = null;
|
|
private localAudioTrack: LocalAudioTrack | null = null;
|
|
|
|
// 获取访问令牌
|
|
async getAccessToken(identity: string, roomName: string): Promise<string> {
|
|
try {
|
|
const response = await fetch(`${TOKEN_SERVER_URL}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
identity,
|
|
roomName,
|
|
apiKey: twilioConfig.apiKey,
|
|
apiSecret: twilioConfig.apiSecret,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Token request failed: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.token;
|
|
} catch (error) {
|
|
console.error('Error getting access token:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 连接到视频房间
|
|
async connectToRoom(options: VideoCallOptions): Promise<Room> {
|
|
try {
|
|
const token = await this.getAccessToken(options.identity, options.roomName);
|
|
|
|
const connectOptions = {
|
|
...videoOptions,
|
|
name: options.roomName,
|
|
audio: options.audio ?? true,
|
|
video: options.video ?? true,
|
|
};
|
|
|
|
this.room = await connect(token, connectOptions);
|
|
|
|
// 设置事件监听器
|
|
this.setupRoomEventListeners();
|
|
|
|
return this.room;
|
|
} catch (error) {
|
|
console.error('Error connecting to room:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 断开连接
|
|
disconnect(): void {
|
|
if (this.room) {
|
|
this.room.disconnect();
|
|
this.room = null;
|
|
}
|
|
|
|
if (this.localVideoTrack) {
|
|
this.localVideoTrack.stop();
|
|
this.localVideoTrack = null;
|
|
}
|
|
|
|
if (this.localAudioTrack) {
|
|
this.localAudioTrack.stop();
|
|
this.localAudioTrack = null;
|
|
}
|
|
}
|
|
|
|
// 切换音频
|
|
toggleAudio(): boolean {
|
|
if (this.room && this.room.localParticipant) {
|
|
const audioTrack = Array.from(this.room.localParticipant.audioTracks.values())[0];
|
|
if (audioTrack) {
|
|
if (audioTrack.track.isEnabled) {
|
|
audioTrack.track.disable();
|
|
} else {
|
|
audioTrack.track.enable();
|
|
}
|
|
return audioTrack.track.isEnabled;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 切换视频
|
|
toggleVideo(): boolean {
|
|
if (this.room && this.room.localParticipant) {
|
|
const videoTrack = Array.from(this.room.localParticipant.videoTracks.values())[0];
|
|
if (videoTrack) {
|
|
if (videoTrack.track.isEnabled) {
|
|
videoTrack.track.disable();
|
|
} else {
|
|
videoTrack.track.enable();
|
|
}
|
|
return videoTrack.track.isEnabled;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 获取参与者信息
|
|
getParticipants(): ParticipantInfo[] {
|
|
if (!this.room) return [];
|
|
|
|
const participants: ParticipantInfo[] = [];
|
|
|
|
// 本地参与者
|
|
const localParticipant = this.room.localParticipant;
|
|
participants.push({
|
|
identity: localParticipant.identity,
|
|
sid: localParticipant.sid,
|
|
isLocal: true,
|
|
audioEnabled: Array.from(localParticipant.audioTracks.values()).some(track => track.track.isEnabled),
|
|
videoEnabled: Array.from(localParticipant.videoTracks.values()).some(track => track.track.isEnabled),
|
|
});
|
|
|
|
// 远程参与者
|
|
this.room.participants.forEach((participant: RemoteParticipant) => {
|
|
participants.push({
|
|
identity: participant.identity,
|
|
sid: participant.sid,
|
|
isLocal: false,
|
|
audioEnabled: Array.from(participant.audioTracks.values()).some(track => track.track && track.track.isEnabled),
|
|
videoEnabled: Array.from(participant.videoTracks.values()).some(track => track.track && track.track.isEnabled),
|
|
});
|
|
});
|
|
|
|
return participants;
|
|
}
|
|
|
|
// 获取当前房间
|
|
getCurrentRoom(): Room | null {
|
|
return this.room;
|
|
}
|
|
|
|
// 设置房间事件监听器
|
|
private setupRoomEventListeners(): void {
|
|
if (!this.room) return;
|
|
|
|
this.room.on('participantConnected', (participant: RemoteParticipant) => {
|
|
console.log('Participant connected:', participant.identity);
|
|
});
|
|
|
|
this.room.on('participantDisconnected', (participant: RemoteParticipant) => {
|
|
console.log('Participant disconnected:', participant.identity);
|
|
});
|
|
|
|
this.room.on('disconnected', (room: Room) => {
|
|
console.log('Disconnected from room:', room.name);
|
|
});
|
|
|
|
this.room.on('reconnecting', (error: any) => {
|
|
console.log('Reconnecting to room...', error);
|
|
});
|
|
|
|
this.room.on('reconnected', () => {
|
|
console.log('Reconnected to room');
|
|
});
|
|
}
|
|
}
|
|
|
|
export const twilioService = new TwilioService();
|