fixed tests
This commit is contained in:
262
public/js/sfu-publisher-simple.ts
Normal file
262
public/js/sfu-publisher-simple.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
interface SFUMessage {
|
||||
type: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
class SFUPublisher {
|
||||
private ws: WebSocket | null = null;
|
||||
private peerConnection: RTCPeerConnection | null = null;
|
||||
private localStream: MediaStream | null = null;
|
||||
private isStreaming = false;
|
||||
|
||||
private rtcConfiguration: RTCConfiguration = {
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' }
|
||||
]
|
||||
};
|
||||
|
||||
// UI Elements
|
||||
private statusElement: HTMLElement;
|
||||
private subscribersCountElement: HTMLElement;
|
||||
private producersCountElement: HTMLElement;
|
||||
private startButton: HTMLButtonElement;
|
||||
private stopButton: HTMLButtonElement;
|
||||
private localVideo: HTMLVideoElement;
|
||||
|
||||
constructor() {
|
||||
this.statusElement = document.getElementById('status')!;
|
||||
this.subscribersCountElement = document.getElementById('subscribersCount')!;
|
||||
this.producersCountElement = document.getElementById('producersCount')!;
|
||||
this.startButton = document.getElementById('startBtn') as HTMLButtonElement;
|
||||
this.stopButton = document.getElementById('stopBtn') as HTMLButtonElement;
|
||||
this.localVideo = document.getElementById('localVideo') as HTMLVideoElement;
|
||||
|
||||
this.setupEventHandlers();
|
||||
}
|
||||
|
||||
private setupEventHandlers(): void {
|
||||
this.startButton.addEventListener('click', () => this.startBroadcasting());
|
||||
this.stopButton.addEventListener('click', () => this.stopBroadcasting());
|
||||
}
|
||||
|
||||
private async startBroadcasting(): Promise<void> {
|
||||
try {
|
||||
this.updateStatus('Connecting to SFU server...', 'waiting');
|
||||
this.setButtonStates(false, false);
|
||||
|
||||
await this.connectToServer();
|
||||
await this.getLocalStream();
|
||||
await this.createPeerConnection();
|
||||
await this.startSFUNegotiation();
|
||||
|
||||
this.isStreaming = true;
|
||||
this.updateStatus('Broadcasting via SFU - Optimized for scalability!', 'connected');
|
||||
this.setButtonStates(false, true);
|
||||
this.updateStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error starting broadcast:', error);
|
||||
this.updateStatus('Failed to start broadcast: ' + error.message, 'disconnected');
|
||||
this.setButtonStates(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private async stopBroadcasting(): Promise<void> {
|
||||
this.isStreaming = false;
|
||||
|
||||
if (this.peerConnection) {
|
||||
this.peerConnection.close();
|
||||
this.peerConnection = null;
|
||||
}
|
||||
|
||||
if (this.localStream) {
|
||||
this.localStream.getTracks().forEach(track => track.stop());
|
||||
this.localStream = null;
|
||||
}
|
||||
|
||||
if (this.localVideo) {
|
||||
this.localVideo.srcObject = null;
|
||||
}
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
this.updateStatus('Broadcast stopped', 'disconnected');
|
||||
this.setButtonStates(true, false);
|
||||
this.resetStats();
|
||||
}
|
||||
|
||||
private async connectToServer(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ws = new WebSocket('ws://localhost:3001?role=publisher');
|
||||
|
||||
this.ws.onopen = () => resolve();
|
||||
this.ws.onerror = (error) => reject(error);
|
||||
|
||||
this.ws.onmessage = async (event) => {
|
||||
try {
|
||||
const message: SFUMessage = JSON.parse(event.data);
|
||||
await this.handleServerMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
if (this.isStreaming) {
|
||||
this.updateStatus('Connection to server lost', 'disconnected');
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async handleServerMessage(message: SFUMessage): Promise<void> {
|
||||
console.log('Received message:', message.type);
|
||||
|
||||
switch (message.type) {
|
||||
case 'join':
|
||||
console.log('Joined as publisher:', message.data);
|
||||
break;
|
||||
case 'routerRtpCapabilities':
|
||||
console.log('Received router capabilities');
|
||||
// In a full SFU implementation, this would configure the device
|
||||
break;
|
||||
case 'webRtcTransportCreated':
|
||||
console.log('Transport created, connecting...');
|
||||
break;
|
||||
case 'webRtcTransportConnected':
|
||||
console.log('Transport connected, starting to produce');
|
||||
break;
|
||||
case 'produced':
|
||||
console.log('Producer created:', message.data.producerId);
|
||||
break;
|
||||
case 'error':
|
||||
console.error('Server error:', message.data.message);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
|
||||
private async getLocalStream(): Promise<void> {
|
||||
this.localStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
width: { ideal: 1280, max: 1920 },
|
||||
height: { ideal: 720, max: 1080 },
|
||||
frameRate: { ideal: 30, max: 60 }
|
||||
},
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true
|
||||
}
|
||||
});
|
||||
|
||||
this.localVideo.srcObject = this.localStream;
|
||||
}
|
||||
|
||||
private async createPeerConnection(): Promise<void> {
|
||||
this.peerConnection = new RTCPeerConnection(this.rtcConfiguration);
|
||||
|
||||
// Add local stream to peer connection
|
||||
if (this.localStream) {
|
||||
this.localStream.getTracks().forEach(track => {
|
||||
this.peerConnection!.addTrack(track, this.localStream!);
|
||||
});
|
||||
}
|
||||
|
||||
this.peerConnection.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
this.sendMessage({
|
||||
type: 'ice-candidate',
|
||||
data: event.candidate
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.peerConnection.onconnectionstatechange = () => {
|
||||
console.log('Connection state:', this.peerConnection?.connectionState);
|
||||
};
|
||||
}
|
||||
|
||||
private async startSFUNegotiation(): Promise<void> {
|
||||
// Simulate SFU negotiation process
|
||||
// In a real SFU implementation, this would involve:
|
||||
// 1. Getting router RTP capabilities
|
||||
// 2. Creating WebRTC transport
|
||||
// 3. Connecting transport
|
||||
// 4. Creating producers for audio/video
|
||||
|
||||
this.sendMessage({ type: 'getRouterRtpCapabilities' });
|
||||
|
||||
// Simulate successful setup
|
||||
setTimeout(() => {
|
||||
this.sendMessage({ type: 'createWebRtcTransport' });
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
this.sendMessage({
|
||||
type: 'connectWebRtcTransport',
|
||||
data: { dtlsParameters: { fingerprints: [], role: 'client' } }
|
||||
});
|
||||
}, 200);
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.localStream) {
|
||||
const videoTrack = this.localStream.getVideoTracks()[0];
|
||||
const audioTrack = this.localStream.getAudioTracks()[0];
|
||||
|
||||
if (videoTrack) {
|
||||
this.sendMessage({
|
||||
type: 'produce',
|
||||
data: {
|
||||
kind: 'video',
|
||||
rtpParameters: { codecs: [], encodings: [] }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (audioTrack) {
|
||||
this.sendMessage({
|
||||
type: 'produce',
|
||||
data: {
|
||||
kind: 'audio',
|
||||
rtpParameters: { codecs: [], encodings: [] }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
private sendMessage(message: SFUMessage): void {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
private updateStatus(status: string, className: string): void {
|
||||
this.statusElement.textContent = status;
|
||||
this.statusElement.className = `status ${className}`;
|
||||
}
|
||||
|
||||
private setButtonStates(startEnabled: boolean, stopEnabled: boolean): void {
|
||||
this.startButton.disabled = !startEnabled;
|
||||
this.stopButton.disabled = !stopEnabled;
|
||||
}
|
||||
|
||||
private updateStats(): void {
|
||||
this.subscribersCountElement.textContent = '0';
|
||||
this.producersCountElement.textContent = this.isStreaming ? '2' : '0';
|
||||
}
|
||||
|
||||
private resetStats(): void {
|
||||
this.subscribersCountElement.textContent = '0';
|
||||
this.producersCountElement.textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
new SFUPublisher();
|
||||
Reference in New Issue
Block a user