I’m writing it in a react type script.
But the part that needs to be executed only once is executed twice, so we are having a problem
The first tsx file with the function you use
<code>import React from 'react';
import * as mediasoupClient from 'mediasoup-client';
import { types } from 'mediasoup-client';
import io from 'socket.io-client';
import { setupSocket, CustomSocket } from './socket';
import {Consumer,ConsumerOptions} from "mediasoup-client/lib/Consumer";
const mediaType = {
audio: 'audioType',
video: 'videoType',
screen: 'screenType'
};
const _EVENTS = {
exitRoom: 'exitRoom',
openRoom: 'openRoom',
startVideo: 'startVideo',
stopVideo: 'stopVideo',
startAudio: 'startAudio',
stopAudio: 'stopAudio',
startScreen: 'startScreen',
stopScreen: 'stopScreen'
};
interface RoomClientProps {
localMediaEl: HTMLDivElement;
remoteVideoEl: HTMLDivElement;
remoteAudioEl: HTMLDivElement;
socketUrl: string;
room_id: string;
name: string;
successCallback: () => void;
}
interface ConsumeResult {
consumer: Consumer;
stream: MediaStream;
kind: 'video' | 'audio';
}
class RoomClient {
private name: string;
private localMediaEl: HTMLVideoElement ;
private remoteVideoEl: HTMLVideoElement ;
private remoteAudioEl: HTMLAudioElement ;
private socket: any;
private producerTransport: types.Transport | null = null;
private consumerTransport: types.Transport | null = null;
private device: mediasoupClient.Device | null = null;
private room_id: string;
private isVideoOnFullScreen: boolean = false;
private isDevicesVisible: boolean = false;
private consumers: Map<string, types.Consumer> = new Map();
private producers: Map<string, types.Producer> = new Map();
private producerLabel: Map<string, string> = new Map();
private _isOpen: boolean = false;
private eventListeners: Map<string, Array<() => void>> = new Map();
static mediaType = {
audio: 'audioType',
video: 'videoType',
screen: 'screenType'
};
constructor(
localMediaEl: HTMLVideoElement,
remoteVideoEl: HTMLVideoElement,
remoteAudioEl: HTMLAudioElement,
mediasoupClientInstance: typeof mediasoupClient,
socketUrl: string,
room_id: string,
name: string,
successCallback: () => void
) {
this.name = name;
this.localMediaEl = localMediaEl;
this.remoteVideoEl = remoteVideoEl;
this.remoteAudioEl = remoteAudioEl;
this.mediasoupClient = mediasoupClientInstance;
this.socket = setupSocket(io(socketUrl));
this.room_id = room_id;
console.log('Mediasoup client', mediasoupClientInstance);
Object.keys(_EVENTS).forEach((evt) => {
this.eventListeners.set(evt, []);
});
this.createRoom(room_id, name).then(async () => {
try {
await this.join(name, room_id);
this.initSockets();
this._isOpen = true;
console.log("create room")
successCallback();
} catch(ex) {
alert('The room is full!');
}
});
}
private mediasoupClient: typeof mediasoupClient;
async start() {
let room_id = this.room_id;
let name = this.name;
await this.socket.request('start', { room_id, name }).catch((err: any) => {
console.log('Start debate error:', err);
});
}
async createRoom(room_id: string, name: string): Promise<void> {
try {
await this.socket.request('createRoom', {
room_id,
name
});
} catch (err) {
console.log('Create room error:', err);
}
}
async join(name: string, room_id: string) {
try {
const e: any = await this.socket.request('join', { name, room_id });
if (e.hasOwnProperty('error')) {
return Promise.reject(new Error('The room is full!'));
}
console.log('Joined to room', e);
const data = await this.socket.request('getRouterRtpCapabilities');
const device = await this.loadDevice(data);
this.device = device;
await this.initTransports(device);
this.socket.emit('getProducers');
} catch (error) {
console.error('Failed to load device:', error);
alert('Failed to join the room');
}
}
async loadDevice(routerRtpCapabilities: mediasoupClient.types.RtpCapabilities): Promise<mediasoupClient.Device> {
let device: mediasoupClient.Device;
console.log("check")
try {
device = new mediasoupClient.Device();
} catch (error: any) {
if (error.name === 'UnsupportedError') {
console.error('Browser not supported');
alert('Browser not supported');
throw error;
} else {
console.error(error);
throw error;
}
}
await device.load({ routerRtpCapabilities });
return device;
}
async initTransports(device: mediasoupClient.Device) {
{
const data = await this.socket.request('createWebRtcTransport', {
forceTcp: false,
rtpCapabilities: device.rtpCapabilities
});
if (data.error) {
console.error(data.error);
return;
}
this.producerTransport = device.createSendTransport(data);
this.producerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
this.socket
.request('connectTransport', {
dtlsParameters,
transport_id: data.id
})
.then(callback)
.catch(errback);
});
this.producerTransport.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
try {
const { producer_id } = await this.socket.request('produce', {
producerTransportId: this.producerTransport!.id,
kind,
rtpParameters
});
callback({ id: producer_id });
} catch (err:any) {
errback(err);
}
});
this.producerTransport.on('connectionstatechange', (state) => {
switch (state) {
case 'connecting':
break;
case 'connected':
break;
case 'failed':
this.producerTransport!.close();
break;
default:
break;
}
});
}
// init consumerTransport
{
const data = await this.socket.request('createWebRtcTransport', {
forceTcp: false
});
if (data.error) {
console.error(data.error);
return;
}
this.consumerTransport = device.createRecvTransport(data);
this.consumerTransport.on('connect', ({ dtlsParameters }, callback, errback) => {
this.socket
.request('connectTransport', {
transport_id: this.consumerTransport!.id,
dtlsParameters
})
.then(callback)
.catch(errback);
});
this.consumerTransport.on('connectionstatechange', async (state) => {
switch (state) {
case 'connecting':
break;
case 'connected':
break;
case 'failed':
this.consumerTransport!.close();
break;
default:
break;
}
});
}
}
initSockets() {
this.socket.on('rule', (data: any) => {
console.log(new Date().toISOString(), data);
});
this.socket.on('consumerClosed', ({ consumer_id }: { consumer_id: string }) => {
console.log('Closing consumer:', consumer_id);
this.removeConsumer(consumer_id);
});
this.socket.on('addUser', (data: any) => {
console.log('New user:', data);
});
this.socket.on('swapUser', (data: any) => {
console.log('Swap user:', data);
});
this.socket.on('removeUser', (data: any) => {
console.log('Remove user:', data);
});
this.socket.on('newProducers', async (data: any) => {
console.log('New producers', data);
for (let { producer_id } of data) {
await this.consume(producer_id);
}
});
this.socket.on('disconnect', () => {
this.exit(true);
});
}
async produce(type: string, deviceId: string | null = null) {
let mediaConstraints: any = {};
let audio = false;
let screen = false;
switch (type) {
case mediaType.audio:
mediaConstraints = {
audio: {
deviceId: deviceId
},
video: false
};
audio = true;
break;
case mediaType.video:
mediaConstraints = {
audio: false,
video: {
width: {
min: 640,
ideal: 640
},
height: {
min: 480,
ideal: 480
},
deviceId: deviceId
}
};
break;
case mediaType.screen:
mediaConstraints = false;
screen = true;
break;
default:
return;
}
if (!this.device!.canProduce('video') && !audio) {
console.error('Cannot produce video');
return;
}
if (this.producerLabel.has(type)) {
console.log('Producer already exists for this type ' + type);
return;
}
console.log('Media constraints:', mediaConstraints);
let stream;
try {
stream = screen ? await (navigator.mediaDevices as any).getDisplayMedia() : await navigator.mediaDevices.getUserMedia(mediaConstraints);
console.log(navigator.mediaDevices.getSupportedConstraints());
const track = audio ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
const params = {
track
};
const producer = await this.producerTransport!.produce(params);
console.log('Producer', producer);
this.producers.set(producer.id, producer);
let elem: HTMLVideoElement;
if (!audio) {
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = producer.id;
elem.playsInline = false;
elem.autoplay = true;
elem.className = 'vid';
this.localMediaEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다")
}
producer.on('trackended', () => {
this.closeProducer(type);
});
producer.on('transportclose', () => {
console.log('Producer transport closed');
if (!audio) {
elem!.parentNode!.removeChild(elem!);
}
this.producers.delete(producer.id);
});
producer.on('@close', () => {
console.log('Closing producer');
if (!audio) {
elem!.parentNode!.removeChild(elem!);
}
this.producers.delete(producer.id);
});
this.producerLabel.set(type, producer.id);
switch (type) {
case mediaType.audio:
this.event(_EVENTS.startAudio);
break;
case mediaType.video:
this.event(_EVENTS.startVideo);
break;
case mediaType.screen:
this.event(_EVENTS.startScreen);
break;
default:
return;
}
} catch (err) {
console.log(err);
}
}
async consume(producer_id: string) {
try {
const { consumer, stream, kind } = await this.getConsumeStream(producer_id);
this.consumers.set(consumer.id, consumer);
let elem: HTMLVideoElement | HTMLAudioElement;
if (kind === 'video') {
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = consumer.id;
if ("playsInline" in elem) {
elem.playsInline = false;
} // For video
elem.autoplay = true;
elem.className = 'vid';
this.remoteVideoEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다 비디오2")
} else {
elem = document.createElement('audio');
elem.srcObject = stream;
elem.id = consumer.id;
elem.autoplay = true;
// Handle playsInline property for audio
if ("playsInline" in elem) {
// Some browsers might support it, so set it to false if available
elem.playsInline = false;
}
this.remoteAudioEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다33333333")
}
consumer.on('trackended', () => {
this.removeConsumer(consumer.id);
});
consumer.on('transportclose', () => {
this.removeConsumer(consumer.id);
});
} catch (error) {
console.error('Error consuming stream:', error);
}
}
async getConsumeStream(producerId: string): Promise<ConsumeResult> {
const { rtpCapabilities } = this.device!;
const data = await this.socket.request('consume', {
rtpCapabilities,
consumerTransportId: this.consumerTransport!.id,
producerId
});
const { id, kind, rtpParameters } = data;
const consumer = await this.consumerTransport!.consume({
id,
producerId,
kind,
rtpParameters,
});
const stream = new MediaStream();
stream.addTrack(consumer.track);
return {
consumer,
stream,
kind
};
}
closeProducer(type: string) {
if (!this.producerLabel.has(type)) {
console.log('No producer found for this type ' + type);
return;
}
let producer_id = this.producerLabel.get(type)!;
console.log(producer_id);
this.socket.emit('producerClosed', {
producer_id
});
this.producers.get(producer_id)!.close();
this.producers.delete(producer_id);
this.producerLabel.delete(type);
switch (type) {
case mediaType.audio:
this.event(_EVENTS.stopAudio);
break;
case mediaType.video:
this.event(_EVENTS.stopVideo);
break;
case mediaType.screen:
this.event(_EVENTS.stopScreen);
break;
default:
return;
}
}
async exit(offline: boolean) {
const clean = () => {
this._isOpen = false;
this.consumerTransport!.close();
this.producerTransport!.close();
this.socket.off('disconnect');
this.socket.off('newProducers');
this.socket.off('consumerClosed');
};
if (!offline) {
this.socket.request('exitRoom').then((e: any) => {
console.log(e);
clean();
}).catch((err: any) => {
console.warn(err);
clean();
});
} else {
clean();
}
this.event(_EVENTS.exitRoom);
}
/////// UTILS ////////
async event(evt: string) {
if (!this.eventListeners.has(evt)) {
console.log('No listeners found for event ' + evt);
return;
}
this.eventListeners.get(evt)!.forEach((callback) => {
callback();
});
}
on(evt: string, callback: () => void) {
this.eventListeners.get(evt)!.push(callback);
}
getLocalProducers() {
return this.producers;
}
removeConsumer(consumer_id: string) {
let elem = document.getElementById(consumer_id);
elem!.parentNode!.removeChild(elem!);
this.consumers.delete(consumer_id);
}
}
export default RoomClient;
</code>
<code>import React from 'react';
import * as mediasoupClient from 'mediasoup-client';
import { types } from 'mediasoup-client';
import io from 'socket.io-client';
import { setupSocket, CustomSocket } from './socket';
import {Consumer,ConsumerOptions} from "mediasoup-client/lib/Consumer";
const mediaType = {
audio: 'audioType',
video: 'videoType',
screen: 'screenType'
};
const _EVENTS = {
exitRoom: 'exitRoom',
openRoom: 'openRoom',
startVideo: 'startVideo',
stopVideo: 'stopVideo',
startAudio: 'startAudio',
stopAudio: 'stopAudio',
startScreen: 'startScreen',
stopScreen: 'stopScreen'
};
interface RoomClientProps {
localMediaEl: HTMLDivElement;
remoteVideoEl: HTMLDivElement;
remoteAudioEl: HTMLDivElement;
socketUrl: string;
room_id: string;
name: string;
successCallback: () => void;
}
interface ConsumeResult {
consumer: Consumer;
stream: MediaStream;
kind: 'video' | 'audio';
}
class RoomClient {
private name: string;
private localMediaEl: HTMLVideoElement ;
private remoteVideoEl: HTMLVideoElement ;
private remoteAudioEl: HTMLAudioElement ;
private socket: any;
private producerTransport: types.Transport | null = null;
private consumerTransport: types.Transport | null = null;
private device: mediasoupClient.Device | null = null;
private room_id: string;
private isVideoOnFullScreen: boolean = false;
private isDevicesVisible: boolean = false;
private consumers: Map<string, types.Consumer> = new Map();
private producers: Map<string, types.Producer> = new Map();
private producerLabel: Map<string, string> = new Map();
private _isOpen: boolean = false;
private eventListeners: Map<string, Array<() => void>> = new Map();
static mediaType = {
audio: 'audioType',
video: 'videoType',
screen: 'screenType'
};
constructor(
localMediaEl: HTMLVideoElement,
remoteVideoEl: HTMLVideoElement,
remoteAudioEl: HTMLAudioElement,
mediasoupClientInstance: typeof mediasoupClient,
socketUrl: string,
room_id: string,
name: string,
successCallback: () => void
) {
this.name = name;
this.localMediaEl = localMediaEl;
this.remoteVideoEl = remoteVideoEl;
this.remoteAudioEl = remoteAudioEl;
this.mediasoupClient = mediasoupClientInstance;
this.socket = setupSocket(io(socketUrl));
this.room_id = room_id;
console.log('Mediasoup client', mediasoupClientInstance);
Object.keys(_EVENTS).forEach((evt) => {
this.eventListeners.set(evt, []);
});
this.createRoom(room_id, name).then(async () => {
try {
await this.join(name, room_id);
this.initSockets();
this._isOpen = true;
console.log("create room")
successCallback();
} catch(ex) {
alert('The room is full!');
}
});
}
private mediasoupClient: typeof mediasoupClient;
async start() {
let room_id = this.room_id;
let name = this.name;
await this.socket.request('start', { room_id, name }).catch((err: any) => {
console.log('Start debate error:', err);
});
}
async createRoom(room_id: string, name: string): Promise<void> {
try {
await this.socket.request('createRoom', {
room_id,
name
});
} catch (err) {
console.log('Create room error:', err);
}
}
async join(name: string, room_id: string) {
try {
const e: any = await this.socket.request('join', { name, room_id });
if (e.hasOwnProperty('error')) {
return Promise.reject(new Error('The room is full!'));
}
console.log('Joined to room', e);
const data = await this.socket.request('getRouterRtpCapabilities');
const device = await this.loadDevice(data);
this.device = device;
await this.initTransports(device);
this.socket.emit('getProducers');
} catch (error) {
console.error('Failed to load device:', error);
alert('Failed to join the room');
}
}
async loadDevice(routerRtpCapabilities: mediasoupClient.types.RtpCapabilities): Promise<mediasoupClient.Device> {
let device: mediasoupClient.Device;
console.log("check")
try {
device = new mediasoupClient.Device();
} catch (error: any) {
if (error.name === 'UnsupportedError') {
console.error('Browser not supported');
alert('Browser not supported');
throw error;
} else {
console.error(error);
throw error;
}
}
await device.load({ routerRtpCapabilities });
return device;
}
async initTransports(device: mediasoupClient.Device) {
{
const data = await this.socket.request('createWebRtcTransport', {
forceTcp: false,
rtpCapabilities: device.rtpCapabilities
});
if (data.error) {
console.error(data.error);
return;
}
this.producerTransport = device.createSendTransport(data);
this.producerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
this.socket
.request('connectTransport', {
dtlsParameters,
transport_id: data.id
})
.then(callback)
.catch(errback);
});
this.producerTransport.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
try {
const { producer_id } = await this.socket.request('produce', {
producerTransportId: this.producerTransport!.id,
kind,
rtpParameters
});
callback({ id: producer_id });
} catch (err:any) {
errback(err);
}
});
this.producerTransport.on('connectionstatechange', (state) => {
switch (state) {
case 'connecting':
break;
case 'connected':
break;
case 'failed':
this.producerTransport!.close();
break;
default:
break;
}
});
}
// init consumerTransport
{
const data = await this.socket.request('createWebRtcTransport', {
forceTcp: false
});
if (data.error) {
console.error(data.error);
return;
}
this.consumerTransport = device.createRecvTransport(data);
this.consumerTransport.on('connect', ({ dtlsParameters }, callback, errback) => {
this.socket
.request('connectTransport', {
transport_id: this.consumerTransport!.id,
dtlsParameters
})
.then(callback)
.catch(errback);
});
this.consumerTransport.on('connectionstatechange', async (state) => {
switch (state) {
case 'connecting':
break;
case 'connected':
break;
case 'failed':
this.consumerTransport!.close();
break;
default:
break;
}
});
}
}
initSockets() {
this.socket.on('rule', (data: any) => {
console.log(new Date().toISOString(), data);
});
this.socket.on('consumerClosed', ({ consumer_id }: { consumer_id: string }) => {
console.log('Closing consumer:', consumer_id);
this.removeConsumer(consumer_id);
});
this.socket.on('addUser', (data: any) => {
console.log('New user:', data);
});
this.socket.on('swapUser', (data: any) => {
console.log('Swap user:', data);
});
this.socket.on('removeUser', (data: any) => {
console.log('Remove user:', data);
});
this.socket.on('newProducers', async (data: any) => {
console.log('New producers', data);
for (let { producer_id } of data) {
await this.consume(producer_id);
}
});
this.socket.on('disconnect', () => {
this.exit(true);
});
}
async produce(type: string, deviceId: string | null = null) {
let mediaConstraints: any = {};
let audio = false;
let screen = false;
switch (type) {
case mediaType.audio:
mediaConstraints = {
audio: {
deviceId: deviceId
},
video: false
};
audio = true;
break;
case mediaType.video:
mediaConstraints = {
audio: false,
video: {
width: {
min: 640,
ideal: 640
},
height: {
min: 480,
ideal: 480
},
deviceId: deviceId
}
};
break;
case mediaType.screen:
mediaConstraints = false;
screen = true;
break;
default:
return;
}
if (!this.device!.canProduce('video') && !audio) {
console.error('Cannot produce video');
return;
}
if (this.producerLabel.has(type)) {
console.log('Producer already exists for this type ' + type);
return;
}
console.log('Media constraints:', mediaConstraints);
let stream;
try {
stream = screen ? await (navigator.mediaDevices as any).getDisplayMedia() : await navigator.mediaDevices.getUserMedia(mediaConstraints);
console.log(navigator.mediaDevices.getSupportedConstraints());
const track = audio ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
const params = {
track
};
const producer = await this.producerTransport!.produce(params);
console.log('Producer', producer);
this.producers.set(producer.id, producer);
let elem: HTMLVideoElement;
if (!audio) {
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = producer.id;
elem.playsInline = false;
elem.autoplay = true;
elem.className = 'vid';
this.localMediaEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다")
}
producer.on('trackended', () => {
this.closeProducer(type);
});
producer.on('transportclose', () => {
console.log('Producer transport closed');
if (!audio) {
elem!.parentNode!.removeChild(elem!);
}
this.producers.delete(producer.id);
});
producer.on('@close', () => {
console.log('Closing producer');
if (!audio) {
elem!.parentNode!.removeChild(elem!);
}
this.producers.delete(producer.id);
});
this.producerLabel.set(type, producer.id);
switch (type) {
case mediaType.audio:
this.event(_EVENTS.startAudio);
break;
case mediaType.video:
this.event(_EVENTS.startVideo);
break;
case mediaType.screen:
this.event(_EVENTS.startScreen);
break;
default:
return;
}
} catch (err) {
console.log(err);
}
}
async consume(producer_id: string) {
try {
const { consumer, stream, kind } = await this.getConsumeStream(producer_id);
this.consumers.set(consumer.id, consumer);
let elem: HTMLVideoElement | HTMLAudioElement;
if (kind === 'video') {
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = consumer.id;
if ("playsInline" in elem) {
elem.playsInline = false;
} // For video
elem.autoplay = true;
elem.className = 'vid';
this.remoteVideoEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다 비디오2")
} else {
elem = document.createElement('audio');
elem.srcObject = stream;
elem.id = consumer.id;
elem.autoplay = true;
// Handle playsInline property for audio
if ("playsInline" in elem) {
// Some browsers might support it, so set it to false if available
elem.playsInline = false;
}
this.remoteAudioEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다33333333")
}
consumer.on('trackended', () => {
this.removeConsumer(consumer.id);
});
consumer.on('transportclose', () => {
this.removeConsumer(consumer.id);
});
} catch (error) {
console.error('Error consuming stream:', error);
}
}
async getConsumeStream(producerId: string): Promise<ConsumeResult> {
const { rtpCapabilities } = this.device!;
const data = await this.socket.request('consume', {
rtpCapabilities,
consumerTransportId: this.consumerTransport!.id,
producerId
});
const { id, kind, rtpParameters } = data;
const consumer = await this.consumerTransport!.consume({
id,
producerId,
kind,
rtpParameters,
});
const stream = new MediaStream();
stream.addTrack(consumer.track);
return {
consumer,
stream,
kind
};
}
closeProducer(type: string) {
if (!this.producerLabel.has(type)) {
console.log('No producer found for this type ' + type);
return;
}
let producer_id = this.producerLabel.get(type)!;
console.log(producer_id);
this.socket.emit('producerClosed', {
producer_id
});
this.producers.get(producer_id)!.close();
this.producers.delete(producer_id);
this.producerLabel.delete(type);
switch (type) {
case mediaType.audio:
this.event(_EVENTS.stopAudio);
break;
case mediaType.video:
this.event(_EVENTS.stopVideo);
break;
case mediaType.screen:
this.event(_EVENTS.stopScreen);
break;
default:
return;
}
}
async exit(offline: boolean) {
const clean = () => {
this._isOpen = false;
this.consumerTransport!.close();
this.producerTransport!.close();
this.socket.off('disconnect');
this.socket.off('newProducers');
this.socket.off('consumerClosed');
};
if (!offline) {
this.socket.request('exitRoom').then((e: any) => {
console.log(e);
clean();
}).catch((err: any) => {
console.warn(err);
clean();
});
} else {
clean();
}
this.event(_EVENTS.exitRoom);
}
/////// UTILS ////////
async event(evt: string) {
if (!this.eventListeners.has(evt)) {
console.log('No listeners found for event ' + evt);
return;
}
this.eventListeners.get(evt)!.forEach((callback) => {
callback();
});
}
on(evt: string, callback: () => void) {
this.eventListeners.get(evt)!.push(callback);
}
getLocalProducers() {
return this.producers;
}
removeConsumer(consumer_id: string) {
let elem = document.getElementById(consumer_id);
elem!.parentNode!.removeChild(elem!);
this.consumers.delete(consumer_id);
}
}
export default RoomClient;
</code>
import React from 'react';
import * as mediasoupClient from 'mediasoup-client';
import { types } from 'mediasoup-client';
import io from 'socket.io-client';
import { setupSocket, CustomSocket } from './socket';
import {Consumer,ConsumerOptions} from "mediasoup-client/lib/Consumer";
const mediaType = {
audio: 'audioType',
video: 'videoType',
screen: 'screenType'
};
const _EVENTS = {
exitRoom: 'exitRoom',
openRoom: 'openRoom',
startVideo: 'startVideo',
stopVideo: 'stopVideo',
startAudio: 'startAudio',
stopAudio: 'stopAudio',
startScreen: 'startScreen',
stopScreen: 'stopScreen'
};
interface RoomClientProps {
localMediaEl: HTMLDivElement;
remoteVideoEl: HTMLDivElement;
remoteAudioEl: HTMLDivElement;
socketUrl: string;
room_id: string;
name: string;
successCallback: () => void;
}
interface ConsumeResult {
consumer: Consumer;
stream: MediaStream;
kind: 'video' | 'audio';
}
class RoomClient {
private name: string;
private localMediaEl: HTMLVideoElement ;
private remoteVideoEl: HTMLVideoElement ;
private remoteAudioEl: HTMLAudioElement ;
private socket: any;
private producerTransport: types.Transport | null = null;
private consumerTransport: types.Transport | null = null;
private device: mediasoupClient.Device | null = null;
private room_id: string;
private isVideoOnFullScreen: boolean = false;
private isDevicesVisible: boolean = false;
private consumers: Map<string, types.Consumer> = new Map();
private producers: Map<string, types.Producer> = new Map();
private producerLabel: Map<string, string> = new Map();
private _isOpen: boolean = false;
private eventListeners: Map<string, Array<() => void>> = new Map();
static mediaType = {
audio: 'audioType',
video: 'videoType',
screen: 'screenType'
};
constructor(
localMediaEl: HTMLVideoElement,
remoteVideoEl: HTMLVideoElement,
remoteAudioEl: HTMLAudioElement,
mediasoupClientInstance: typeof mediasoupClient,
socketUrl: string,
room_id: string,
name: string,
successCallback: () => void
) {
this.name = name;
this.localMediaEl = localMediaEl;
this.remoteVideoEl = remoteVideoEl;
this.remoteAudioEl = remoteAudioEl;
this.mediasoupClient = mediasoupClientInstance;
this.socket = setupSocket(io(socketUrl));
this.room_id = room_id;
console.log('Mediasoup client', mediasoupClientInstance);
Object.keys(_EVENTS).forEach((evt) => {
this.eventListeners.set(evt, []);
});
this.createRoom(room_id, name).then(async () => {
try {
await this.join(name, room_id);
this.initSockets();
this._isOpen = true;
console.log("create room")
successCallback();
} catch(ex) {
alert('The room is full!');
}
});
}
private mediasoupClient: typeof mediasoupClient;
async start() {
let room_id = this.room_id;
let name = this.name;
await this.socket.request('start', { room_id, name }).catch((err: any) => {
console.log('Start debate error:', err);
});
}
async createRoom(room_id: string, name: string): Promise<void> {
try {
await this.socket.request('createRoom', {
room_id,
name
});
} catch (err) {
console.log('Create room error:', err);
}
}
async join(name: string, room_id: string) {
try {
const e: any = await this.socket.request('join', { name, room_id });
if (e.hasOwnProperty('error')) {
return Promise.reject(new Error('The room is full!'));
}
console.log('Joined to room', e);
const data = await this.socket.request('getRouterRtpCapabilities');
const device = await this.loadDevice(data);
this.device = device;
await this.initTransports(device);
this.socket.emit('getProducers');
} catch (error) {
console.error('Failed to load device:', error);
alert('Failed to join the room');
}
}
async loadDevice(routerRtpCapabilities: mediasoupClient.types.RtpCapabilities): Promise<mediasoupClient.Device> {
let device: mediasoupClient.Device;
console.log("check")
try {
device = new mediasoupClient.Device();
} catch (error: any) {
if (error.name === 'UnsupportedError') {
console.error('Browser not supported');
alert('Browser not supported');
throw error;
} else {
console.error(error);
throw error;
}
}
await device.load({ routerRtpCapabilities });
return device;
}
async initTransports(device: mediasoupClient.Device) {
{
const data = await this.socket.request('createWebRtcTransport', {
forceTcp: false,
rtpCapabilities: device.rtpCapabilities
});
if (data.error) {
console.error(data.error);
return;
}
this.producerTransport = device.createSendTransport(data);
this.producerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
this.socket
.request('connectTransport', {
dtlsParameters,
transport_id: data.id
})
.then(callback)
.catch(errback);
});
this.producerTransport.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
try {
const { producer_id } = await this.socket.request('produce', {
producerTransportId: this.producerTransport!.id,
kind,
rtpParameters
});
callback({ id: producer_id });
} catch (err:any) {
errback(err);
}
});
this.producerTransport.on('connectionstatechange', (state) => {
switch (state) {
case 'connecting':
break;
case 'connected':
break;
case 'failed':
this.producerTransport!.close();
break;
default:
break;
}
});
}
// init consumerTransport
{
const data = await this.socket.request('createWebRtcTransport', {
forceTcp: false
});
if (data.error) {
console.error(data.error);
return;
}
this.consumerTransport = device.createRecvTransport(data);
this.consumerTransport.on('connect', ({ dtlsParameters }, callback, errback) => {
this.socket
.request('connectTransport', {
transport_id: this.consumerTransport!.id,
dtlsParameters
})
.then(callback)
.catch(errback);
});
this.consumerTransport.on('connectionstatechange', async (state) => {
switch (state) {
case 'connecting':
break;
case 'connected':
break;
case 'failed':
this.consumerTransport!.close();
break;
default:
break;
}
});
}
}
initSockets() {
this.socket.on('rule', (data: any) => {
console.log(new Date().toISOString(), data);
});
this.socket.on('consumerClosed', ({ consumer_id }: { consumer_id: string }) => {
console.log('Closing consumer:', consumer_id);
this.removeConsumer(consumer_id);
});
this.socket.on('addUser', (data: any) => {
console.log('New user:', data);
});
this.socket.on('swapUser', (data: any) => {
console.log('Swap user:', data);
});
this.socket.on('removeUser', (data: any) => {
console.log('Remove user:', data);
});
this.socket.on('newProducers', async (data: any) => {
console.log('New producers', data);
for (let { producer_id } of data) {
await this.consume(producer_id);
}
});
this.socket.on('disconnect', () => {
this.exit(true);
});
}
async produce(type: string, deviceId: string | null = null) {
let mediaConstraints: any = {};
let audio = false;
let screen = false;
switch (type) {
case mediaType.audio:
mediaConstraints = {
audio: {
deviceId: deviceId
},
video: false
};
audio = true;
break;
case mediaType.video:
mediaConstraints = {
audio: false,
video: {
width: {
min: 640,
ideal: 640
},
height: {
min: 480,
ideal: 480
},
deviceId: deviceId
}
};
break;
case mediaType.screen:
mediaConstraints = false;
screen = true;
break;
default:
return;
}
if (!this.device!.canProduce('video') && !audio) {
console.error('Cannot produce video');
return;
}
if (this.producerLabel.has(type)) {
console.log('Producer already exists for this type ' + type);
return;
}
console.log('Media constraints:', mediaConstraints);
let stream;
try {
stream = screen ? await (navigator.mediaDevices as any).getDisplayMedia() : await navigator.mediaDevices.getUserMedia(mediaConstraints);
console.log(navigator.mediaDevices.getSupportedConstraints());
const track = audio ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
const params = {
track
};
const producer = await this.producerTransport!.produce(params);
console.log('Producer', producer);
this.producers.set(producer.id, producer);
let elem: HTMLVideoElement;
if (!audio) {
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = producer.id;
elem.playsInline = false;
elem.autoplay = true;
elem.className = 'vid';
this.localMediaEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다")
}
producer.on('trackended', () => {
this.closeProducer(type);
});
producer.on('transportclose', () => {
console.log('Producer transport closed');
if (!audio) {
elem!.parentNode!.removeChild(elem!);
}
this.producers.delete(producer.id);
});
producer.on('@close', () => {
console.log('Closing producer');
if (!audio) {
elem!.parentNode!.removeChild(elem!);
}
this.producers.delete(producer.id);
});
this.producerLabel.set(type, producer.id);
switch (type) {
case mediaType.audio:
this.event(_EVENTS.startAudio);
break;
case mediaType.video:
this.event(_EVENTS.startVideo);
break;
case mediaType.screen:
this.event(_EVENTS.startScreen);
break;
default:
return;
}
} catch (err) {
console.log(err);
}
}
async consume(producer_id: string) {
try {
const { consumer, stream, kind } = await this.getConsumeStream(producer_id);
this.consumers.set(consumer.id, consumer);
let elem: HTMLVideoElement | HTMLAudioElement;
if (kind === 'video') {
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = consumer.id;
if ("playsInline" in elem) {
elem.playsInline = false;
} // For video
elem.autoplay = true;
elem.className = 'vid';
this.remoteVideoEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다 비디오2")
} else {
elem = document.createElement('audio');
elem.srcObject = stream;
elem.id = consumer.id;
elem.autoplay = true;
// Handle playsInline property for audio
if ("playsInline" in elem) {
// Some browsers might support it, so set it to false if available
elem.playsInline = false;
}
this.remoteAudioEl.appendChild(elem);
console.log(elem+ "일단은 눈에 띄어야한다33333333")
}
consumer.on('trackended', () => {
this.removeConsumer(consumer.id);
});
consumer.on('transportclose', () => {
this.removeConsumer(consumer.id);
});
} catch (error) {
console.error('Error consuming stream:', error);
}
}
async getConsumeStream(producerId: string): Promise<ConsumeResult> {
const { rtpCapabilities } = this.device!;
const data = await this.socket.request('consume', {
rtpCapabilities,
consumerTransportId: this.consumerTransport!.id,
producerId
});
const { id, kind, rtpParameters } = data;
const consumer = await this.consumerTransport!.consume({
id,
producerId,
kind,
rtpParameters,
});
const stream = new MediaStream();
stream.addTrack(consumer.track);
return {
consumer,
stream,
kind
};
}
closeProducer(type: string) {
if (!this.producerLabel.has(type)) {
console.log('No producer found for this type ' + type);
return;
}
let producer_id = this.producerLabel.get(type)!;
console.log(producer_id);
this.socket.emit('producerClosed', {
producer_id
});
this.producers.get(producer_id)!.close();
this.producers.delete(producer_id);
this.producerLabel.delete(type);
switch (type) {
case mediaType.audio:
this.event(_EVENTS.stopAudio);
break;
case mediaType.video:
this.event(_EVENTS.stopVideo);
break;
case mediaType.screen:
this.event(_EVENTS.stopScreen);
break;
default:
return;
}
}
async exit(offline: boolean) {
const clean = () => {
this._isOpen = false;
this.consumerTransport!.close();
this.producerTransport!.close();
this.socket.off('disconnect');
this.socket.off('newProducers');
this.socket.off('consumerClosed');
};
if (!offline) {
this.socket.request('exitRoom').then((e: any) => {
console.log(e);
clean();
}).catch((err: any) => {
console.warn(err);
clean();
});
} else {
clean();
}
this.event(_EVENTS.exitRoom);
}
/////// UTILS ////////
async event(evt: string) {
if (!this.eventListeners.has(evt)) {
console.log('No listeners found for event ' + evt);
return;
}
this.eventListeners.get(evt)!.forEach((callback) => {
callback();
});
}
on(evt: string, callback: () => void) {
this.eventListeners.get(evt)!.push(callback);
}
getLocalProducers() {
return this.producers;
}
removeConsumer(consumer_id: string) {
let elem = document.getElementById(consumer_id);
elem!.parentNode!.removeChild(elem!);
this.consumers.delete(consumer_id);
}
}
export default RoomClient;
Files with functions used by setting.tsx
<code>import { Socket } from 'socket.io-client';
import React, { useRef, useState } from 'react';
import RoomClient from "./RoomClient";
import { types } from 'mediasoup-client';
interface SocketData {
error?: string;
[key: string]: any;
}
export function joinRoom(name: string, room_id: string, audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement, localMediaEl: HTMLVideoElement,
remoteVideoEl: HTMLVideoElement,
remoteAudioEl: HTMLAudioElement,
mediasoupClientInstance: any,
socketUrl: string,
successCallback: () => void) {
if (rc && rc.isOpen()) {
console.log('Already connected to a room');
} else {
console.log('joinjoinhjoin');
initEnumerateDevices(audioSelect,videoSelect);
rc = new RoomClient(localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClientInstance, socketUrl, room_id, name, successCallback);
}
}
export function initEnumerateDevices(audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement) {
console.log("initinitinit0")
// Many browsers, without the consent of getUserMedia, cannot enumerate the devices.
if (isEnumerateDevices) return;
const constraints = {
audio: true,
video: true
};
navigator.mediaDevices
.getUserMedia(constraints)
.then((stream: MediaStream) => {
enumerateDevices(audioSelect, videoSelect);
stream.getTracks().forEach(function (track: MediaStreamTrack) {
track.stop();
});
console.log("initinitinit")
})
.catch((err: any) => {
console.error('Access denied for audio/video: ', err);
});
}
export function enumerateDevices(audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement) {
// Load mediaDevice options
navigator.mediaDevices.enumerateDevices().then((devices: MediaDeviceInfo[]) =>
devices.forEach((device) => {
let el = null;
if ('audioinput' === device.kind) {
el = audioSelect;
} else if ('videoinput' === device.kind) {
el = videoSelect;
}
if (!el) return;
let option = document.createElement('option');
option.value = device.deviceId || '';
option.innerText = device.label || '';
el.appendChild(option);
isEnumerateDevices = true;
})
);
}
</code>
<code>import { Socket } from 'socket.io-client';
import React, { useRef, useState } from 'react';
import RoomClient from "./RoomClient";
import { types } from 'mediasoup-client';
interface SocketData {
error?: string;
[key: string]: any;
}
export function joinRoom(name: string, room_id: string, audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement, localMediaEl: HTMLVideoElement,
remoteVideoEl: HTMLVideoElement,
remoteAudioEl: HTMLAudioElement,
mediasoupClientInstance: any,
socketUrl: string,
successCallback: () => void) {
if (rc && rc.isOpen()) {
console.log('Already connected to a room');
} else {
console.log('joinjoinhjoin');
initEnumerateDevices(audioSelect,videoSelect);
rc = new RoomClient(localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClientInstance, socketUrl, room_id, name, successCallback);
}
}
export function initEnumerateDevices(audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement) {
console.log("initinitinit0")
// Many browsers, without the consent of getUserMedia, cannot enumerate the devices.
if (isEnumerateDevices) return;
const constraints = {
audio: true,
video: true
};
navigator.mediaDevices
.getUserMedia(constraints)
.then((stream: MediaStream) => {
enumerateDevices(audioSelect, videoSelect);
stream.getTracks().forEach(function (track: MediaStreamTrack) {
track.stop();
});
console.log("initinitinit")
})
.catch((err: any) => {
console.error('Access denied for audio/video: ', err);
});
}
export function enumerateDevices(audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement) {
// Load mediaDevice options
navigator.mediaDevices.enumerateDevices().then((devices: MediaDeviceInfo[]) =>
devices.forEach((device) => {
let el = null;
if ('audioinput' === device.kind) {
el = audioSelect;
} else if ('videoinput' === device.kind) {
el = videoSelect;
}
if (!el) return;
let option = document.createElement('option');
option.value = device.deviceId || '';
option.innerText = device.label || '';
el.appendChild(option);
isEnumerateDevices = true;
})
);
}
</code>
import { Socket } from 'socket.io-client';
import React, { useRef, useState } from 'react';
import RoomClient from "./RoomClient";
import { types } from 'mediasoup-client';
interface SocketData {
error?: string;
[key: string]: any;
}
export function joinRoom(name: string, room_id: string, audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement, localMediaEl: HTMLVideoElement,
remoteVideoEl: HTMLVideoElement,
remoteAudioEl: HTMLAudioElement,
mediasoupClientInstance: any,
socketUrl: string,
successCallback: () => void) {
if (rc && rc.isOpen()) {
console.log('Already connected to a room');
} else {
console.log('joinjoinhjoin');
initEnumerateDevices(audioSelect,videoSelect);
rc = new RoomClient(localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClientInstance, socketUrl, room_id, name, successCallback);
}
}
export function initEnumerateDevices(audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement) {
console.log("initinitinit0")
// Many browsers, without the consent of getUserMedia, cannot enumerate the devices.
if (isEnumerateDevices) return;
const constraints = {
audio: true,
video: true
};
navigator.mediaDevices
.getUserMedia(constraints)
.then((stream: MediaStream) => {
enumerateDevices(audioSelect, videoSelect);
stream.getTracks().forEach(function (track: MediaStreamTrack) {
track.stop();
});
console.log("initinitinit")
})
.catch((err: any) => {
console.error('Access denied for audio/video: ', err);
});
}
export function enumerateDevices(audioSelect: HTMLOptionElement, videoSelect: HTMLOptionElement) {
// Load mediaDevice options
navigator.mediaDevices.enumerateDevices().then((devices: MediaDeviceInfo[]) =>
devices.forEach((device) => {
let el = null;
if ('audioinput' === device.kind) {
el = audioSelect;
} else if ('videoinput' === device.kind) {
el = videoSelect;
}
if (!el) return;
let option = document.createElement('option');
option.value = device.deviceId || '';
option.innerText = device.label || '';
el.appendChild(option);
isEnumerateDevices = true;
})
);
}
Before going to the discussion room, setting.tsx
<code> const onClickCreate = async () => {
navigate('/debateRoom', {
state: {
name,
selectedMicDevice: selectedMicDeviceRef.current?.value,
selectedAudioDevice: selectedAudioDeviceRef.current?.value,
selectedVideoDevice: selectedVideoDeviceRef.current?.value
}
});
console.log(selectedAudioDeviceRef.current);
console.log(selectedVideoDeviceRef.current);
if (selectedAudioDeviceRef.current&&selectedVideoDeviceRef.current) {
console.log(remoteVideoEl+"remotevideo확인")
joinRoom(name, "frontend", selectedAudioDeviceRef.current, selectedVideoDeviceRef.current, localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClient, "http://localhost:3001", successCallback);
}
console.log(name);
};
</code>
<code> const onClickCreate = async () => {
navigate('/debateRoom', {
state: {
name,
selectedMicDevice: selectedMicDeviceRef.current?.value,
selectedAudioDevice: selectedAudioDeviceRef.current?.value,
selectedVideoDevice: selectedVideoDeviceRef.current?.value
}
});
console.log(selectedAudioDeviceRef.current);
console.log(selectedVideoDeviceRef.current);
if (selectedAudioDeviceRef.current&&selectedVideoDeviceRef.current) {
console.log(remoteVideoEl+"remotevideo확인")
joinRoom(name, "frontend", selectedAudioDeviceRef.current, selectedVideoDeviceRef.current, localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClient, "http://localhost:3001", successCallback);
}
console.log(name);
};
</code>
const onClickCreate = async () => {
navigate('/debateRoom', {
state: {
name,
selectedMicDevice: selectedMicDeviceRef.current?.value,
selectedAudioDevice: selectedAudioDeviceRef.current?.value,
selectedVideoDevice: selectedVideoDeviceRef.current?.value
}
});
console.log(selectedAudioDeviceRef.current);
console.log(selectedVideoDeviceRef.current);
if (selectedAudioDeviceRef.current&&selectedVideoDeviceRef.current) {
console.log(remoteVideoEl+"remotevideo확인")
joinRoom(name, "frontend", selectedAudioDeviceRef.current, selectedVideoDeviceRef.current, localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClient, "http://localhost:3001", successCallback);
}
console.log(name);
};
Debit room tsx file where the problem occurs
<code>import React, {useEffect, useRef, useState} from 'react';
import {useLocation, useNavigate} from "react-router-dom";
import "./DebateRoom.css"
import DebateFin from "./DebateFin/DebateFin";
import RoomClient from "../../socket/RoomClient";
import {rc} from "../../socket/socket";
interface DebateRoomProps {
onLeave: () => void;
}
const DebateRoom: React.FC<DebateRoomProps> = ({onLeave}) => {
const [progress, setProgress] = React.useState(0);
const [timeLeft, setTimeLeft] = useState('3:00');
const navigate = useNavigate();
const location = useLocation();
const { selectedMicDevice, selectedAudioDevice, selectedVideoDevice } = location.state;
const videoRef = useRef<HTMLVideoElement>(null);
const leave = () => {
navigate('/dashboard');
};
const [soundClicked, setSoundClicked] = useState(false);
const [videoClicked, setVideoClicked] = useState(false);
const [micClicked, setMicClicked] = useState(false);
const [DebateClicked, setDebateClicked] = useState(false);
const [DebateText, setDebateText] = useState("토론시작");
const [soundImg, setSoundImg] = useState(speaker);
const [videoImg, setVideoImg] = useState(video);
const [micImg, setMicImg] = useState(mic);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
const initVideo = async () => {
try {
console.log("device start")
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
console.log(selectedVideoDevice + " video");
console.log(selectedAudioDevice + " audio");
await rc.produce(RoomClient.mediaType.video, selectedVideoDevice);
await rc.produce(RoomClient.mediaType.audio, selectedAudioDevice);
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
initVideo();
}, []);
useEffect(() => {
const toggleAudio = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = (videoRef.current.srcObject as MediaStream).getTracks();
tracks.forEach(track => {
if (track.kind === 'audio') {
track.enabled = !soundClicked;
}
});
}
};
const toggleMicClicked = () => {
if (soundClicked) {
setMicImg(micMute);
setMicClicked(true); // soundClicked가 true이면 micClicked를 true로 설정
}
};
const toggleMicUnclicked = () => {
if (soundClicked) {
setMicImg(mic);
setMicClicked(false);
}
};
toggleAudio();
toggleMicClicked();
return () => {
toggleMicUnclicked();
toggleAudio();
};
}, [soundClicked]);
useEffect(() => {
const toggleMicrophone = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = (videoRef.current.srcObject as MediaStream).getAudioTracks();
tracks.forEach(track => {
track.enabled = !micClicked;
});
}
};
const toggleInsteadClicked = () => {
if (micClicked) {
if (soundClicked) {
setSoundImg(speaker);
setSoundClicked(false);
}
}
};
toggleMicrophone();
return () => {
toggleInsteadClicked();
toggleMicrophone();
};
}, [micClicked]);
useEffect(() => {
const initA1Video = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
const toggleVideo = () => {
if (videoRef.current) {
if (videoClicked) {
videoRef.current.srcObject = null;
} else {
initA1Video();
}
}
};
toggleVideo();
return () => {
};
}, [videoClicked]);
return (
<div>
<div className="AteamArea">
<div className="A1">
<video className="Cam" ref={videoRef} autoPlay width="526px" height="332px" ></video>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? 'long-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{options.map((option) => (
<MenuItem
key={option}
selected={option === 'A1'}
onClick={() => handleMenuItemClick(option)}
>
{option}
</MenuItem>
))}
</Menu>
</div>
</div>
<div className="btnAndCode">
<div className="InviteCode">
<text className="invite">토론방 참여 코드</text>
<text className="code">abc-def-ghi</text>
</div>
<div className="debateBtn">
<button className={DebateClicked ? "RoomDebateClick":"RoomDebateSet"} onClick={DebateClick}>
<text className="debateText" >{DebateText}</text>
</button>
</div>
</div>
</div>
);
};
export default DebateRoom;
</code>
<code>import React, {useEffect, useRef, useState} from 'react';
import {useLocation, useNavigate} from "react-router-dom";
import "./DebateRoom.css"
import DebateFin from "./DebateFin/DebateFin";
import RoomClient from "../../socket/RoomClient";
import {rc} from "../../socket/socket";
interface DebateRoomProps {
onLeave: () => void;
}
const DebateRoom: React.FC<DebateRoomProps> = ({onLeave}) => {
const [progress, setProgress] = React.useState(0);
const [timeLeft, setTimeLeft] = useState('3:00');
const navigate = useNavigate();
const location = useLocation();
const { selectedMicDevice, selectedAudioDevice, selectedVideoDevice } = location.state;
const videoRef = useRef<HTMLVideoElement>(null);
const leave = () => {
navigate('/dashboard');
};
const [soundClicked, setSoundClicked] = useState(false);
const [videoClicked, setVideoClicked] = useState(false);
const [micClicked, setMicClicked] = useState(false);
const [DebateClicked, setDebateClicked] = useState(false);
const [DebateText, setDebateText] = useState("토론시작");
const [soundImg, setSoundImg] = useState(speaker);
const [videoImg, setVideoImg] = useState(video);
const [micImg, setMicImg] = useState(mic);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
const initVideo = async () => {
try {
console.log("device start")
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
console.log(selectedVideoDevice + " video");
console.log(selectedAudioDevice + " audio");
await rc.produce(RoomClient.mediaType.video, selectedVideoDevice);
await rc.produce(RoomClient.mediaType.audio, selectedAudioDevice);
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
initVideo();
}, []);
useEffect(() => {
const toggleAudio = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = (videoRef.current.srcObject as MediaStream).getTracks();
tracks.forEach(track => {
if (track.kind === 'audio') {
track.enabled = !soundClicked;
}
});
}
};
const toggleMicClicked = () => {
if (soundClicked) {
setMicImg(micMute);
setMicClicked(true); // soundClicked가 true이면 micClicked를 true로 설정
}
};
const toggleMicUnclicked = () => {
if (soundClicked) {
setMicImg(mic);
setMicClicked(false);
}
};
toggleAudio();
toggleMicClicked();
return () => {
toggleMicUnclicked();
toggleAudio();
};
}, [soundClicked]);
useEffect(() => {
const toggleMicrophone = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = (videoRef.current.srcObject as MediaStream).getAudioTracks();
tracks.forEach(track => {
track.enabled = !micClicked;
});
}
};
const toggleInsteadClicked = () => {
if (micClicked) {
if (soundClicked) {
setSoundImg(speaker);
setSoundClicked(false);
}
}
};
toggleMicrophone();
return () => {
toggleInsteadClicked();
toggleMicrophone();
};
}, [micClicked]);
useEffect(() => {
const initA1Video = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
const toggleVideo = () => {
if (videoRef.current) {
if (videoClicked) {
videoRef.current.srcObject = null;
} else {
initA1Video();
}
}
};
toggleVideo();
return () => {
};
}, [videoClicked]);
return (
<div>
<div className="AteamArea">
<div className="A1">
<video className="Cam" ref={videoRef} autoPlay width="526px" height="332px" ></video>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? 'long-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{options.map((option) => (
<MenuItem
key={option}
selected={option === 'A1'}
onClick={() => handleMenuItemClick(option)}
>
{option}
</MenuItem>
))}
</Menu>
</div>
</div>
<div className="btnAndCode">
<div className="InviteCode">
<text className="invite">토론방 참여 코드</text>
<text className="code">abc-def-ghi</text>
</div>
<div className="debateBtn">
<button className={DebateClicked ? "RoomDebateClick":"RoomDebateSet"} onClick={DebateClick}>
<text className="debateText" >{DebateText}</text>
</button>
</div>
</div>
</div>
);
};
export default DebateRoom;
</code>
import React, {useEffect, useRef, useState} from 'react';
import {useLocation, useNavigate} from "react-router-dom";
import "./DebateRoom.css"
import DebateFin from "./DebateFin/DebateFin";
import RoomClient from "../../socket/RoomClient";
import {rc} from "../../socket/socket";
interface DebateRoomProps {
onLeave: () => void;
}
const DebateRoom: React.FC<DebateRoomProps> = ({onLeave}) => {
const [progress, setProgress] = React.useState(0);
const [timeLeft, setTimeLeft] = useState('3:00');
const navigate = useNavigate();
const location = useLocation();
const { selectedMicDevice, selectedAudioDevice, selectedVideoDevice } = location.state;
const videoRef = useRef<HTMLVideoElement>(null);
const leave = () => {
navigate('/dashboard');
};
const [soundClicked, setSoundClicked] = useState(false);
const [videoClicked, setVideoClicked] = useState(false);
const [micClicked, setMicClicked] = useState(false);
const [DebateClicked, setDebateClicked] = useState(false);
const [DebateText, setDebateText] = useState("토론시작");
const [soundImg, setSoundImg] = useState(speaker);
const [videoImg, setVideoImg] = useState(video);
const [micImg, setMicImg] = useState(mic);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
const initVideo = async () => {
try {
console.log("device start")
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
console.log(selectedVideoDevice + " video");
console.log(selectedAudioDevice + " audio");
await rc.produce(RoomClient.mediaType.video, selectedVideoDevice);
await rc.produce(RoomClient.mediaType.audio, selectedAudioDevice);
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
initVideo();
}, []);
useEffect(() => {
const toggleAudio = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = (videoRef.current.srcObject as MediaStream).getTracks();
tracks.forEach(track => {
if (track.kind === 'audio') {
track.enabled = !soundClicked;
}
});
}
};
const toggleMicClicked = () => {
if (soundClicked) {
setMicImg(micMute);
setMicClicked(true); // soundClicked가 true이면 micClicked를 true로 설정
}
};
const toggleMicUnclicked = () => {
if (soundClicked) {
setMicImg(mic);
setMicClicked(false);
}
};
toggleAudio();
toggleMicClicked();
return () => {
toggleMicUnclicked();
toggleAudio();
};
}, [soundClicked]);
useEffect(() => {
const toggleMicrophone = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = (videoRef.current.srcObject as MediaStream).getAudioTracks();
tracks.forEach(track => {
track.enabled = !micClicked;
});
}
};
const toggleInsteadClicked = () => {
if (micClicked) {
if (soundClicked) {
setSoundImg(speaker);
setSoundClicked(false);
}
}
};
toggleMicrophone();
return () => {
toggleInsteadClicked();
toggleMicrophone();
};
}, [micClicked]);
useEffect(() => {
const initA1Video = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
const toggleVideo = () => {
if (videoRef.current) {
if (videoClicked) {
videoRef.current.srcObject = null;
} else {
initA1Video();
}
}
};
toggleVideo();
return () => {
};
}, [videoClicked]);
return (
<div>
<div className="AteamArea">
<div className="A1">
<video className="Cam" ref={videoRef} autoPlay width="526px" height="332px" ></video>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? 'long-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{options.map((option) => (
<MenuItem
key={option}
selected={option === 'A1'}
onClick={() => handleMenuItemClick(option)}
>
{option}
</MenuItem>
))}
</Menu>
</div>
</div>
<div className="btnAndCode">
<div className="InviteCode">
<text className="invite">토론방 참여 코드</text>
<text className="code">abc-def-ghi</text>
</div>
<div className="debateBtn">
<button className={DebateClicked ? "RoomDebateClick":"RoomDebateSet"} onClick={DebateClick}>
<text className="debateText" >{DebateText}</text>
</button>
</div>
</div>
</div>
);
};
export default DebateRoom;
I have a problem in debateRoom.tsx where useEffect with produce runs 2 times. What is the problem?
It’s the part that runs twice in debateRoom
<code>useEffect(() => {
const initVideo = async () => {
try {
console.log("device start")
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
console.log(selectedVideoDevice + " video");
console.log(selectedAudioDevice + " audio");
await rc.produce(RoomClient.mediaType.video, selectedVideoDevice);
await rc.produce(RoomClient.mediaType.audio, selectedAudioDevice);
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
initVideo();
}, []);
</code>
<code>useEffect(() => {
const initVideo = async () => {
try {
console.log("device start")
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
console.log(selectedVideoDevice + " video");
console.log(selectedAudioDevice + " audio");
await rc.produce(RoomClient.mediaType.video, selectedVideoDevice);
await rc.produce(RoomClient.mediaType.audio, selectedAudioDevice);
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
initVideo();
}, []);
</code>
useEffect(() => {
const initVideo = async () => {
try {
console.log("device start")
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (videoRef.current) {
console.log(selectedVideoDevice + " video");
console.log(selectedAudioDevice + " audio");
await rc.produce(RoomClient.mediaType.video, selectedVideoDevice);
await rc.produce(RoomClient.mediaType.audio, selectedAudioDevice);
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing local media:', error);
}
};
initVideo();
}, []);
This part should be done only once