diff --git a/config/signaling-server-prod.yml b/config/signaling-server-prod.yml index 4aa1205b..8764452c 100644 --- a/config/signaling-server-prod.yml +++ b/config/signaling-server-prod.yml @@ -22,4 +22,8 @@ cloud: spring: kafka: - bootstrap-servers: '{cipher}276ada73fdb90a68125e2dcce993b77384c8c80d672a78b26eefda56bf60f808f77d486f86d9f1de94e6cb342711bb6211dd8f6cbf98b01e4ece80b938b40acf1cfef75ec74c910f32e68873b06dd3d5' \ No newline at end of file + bootstrap-servers: '{cipher}276ada73fdb90a68125e2dcce993b77384c8c80d672a78b26eefda56bf60f808f77d486f86d9f1de94e6cb342711bb6211dd8f6cbf98b01e4ece80b938b40acf1cfef75ec74c910f32e68873b06dd3d5' + +service: + member: + url: '{cipher}cfc51ed26a533a9c2dea91e247fab25cd2b6d7487d51884ef835d030b9b38373a30bf3596005dbaf2bd82921ee1f85f5614e0e3e4ed0c658b38950b544ceb5c5' \ No newline at end of file diff --git a/src/backend/apigateway-server/src/main/resources/application-local.yml b/src/backend/apigateway-server/src/main/resources/application-local.yml index 8cca107c..a3a332aa 100644 --- a/src/backend/apigateway-server/src/main/resources/application-local.yml +++ b/src/backend/apigateway-server/src/main/resources/application-local.yml @@ -93,12 +93,20 @@ spring: - AuthorizationHeaderFilter - id: signaling-server - uri: lb://SIGNALING-SERVER + uri: ws://SIGNALING-SERVER predicates: - - Path=/signalings/signal + - Path=/signalings/** filters: - RemoveRequestHeader=Cookie - - RewritePath=/signalings/(?.*), /$\{segment} + - RewritePath=/signalings/(?.*), /$\{segment} + + - id: signaling-server + uri: wss://SIGNALING-SERVER + predicates: + - Path=/signalings/** + filters: + - RemoveRequestHeader=Cookie + - RewritePath=/signalings/(?.*), /$\{segment} default-filters: - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials diff --git a/src/backend/apigateway-server/src/main/resources/application-prod.yml b/src/backend/apigateway-server/src/main/resources/application-prod.yml index 8cca107c..d700e371 100644 --- a/src/backend/apigateway-server/src/main/resources/application-prod.yml +++ b/src/backend/apigateway-server/src/main/resources/application-prod.yml @@ -90,15 +90,23 @@ spring: filters: - RemoveRequestHeader=Cookie - RewritePath=/signalings/(?.*), /$\{segment} - - AuthorizationHeaderFilter + - AuthorizationHeaderFilter - id: signaling-server - uri: lb://SIGNALING-SERVER + uri: ws://SIGNALING-SERVER + predicates: + - Path=/signalings/** + filters: + - RemoveRequestHeader=Cookie + - RewritePath=/signalings/(?.*), /$\{segment} + + - id: signaling-server + uri: wss://SIGNALING-SERVER predicates: - - Path=/signalings/signal + - Path=/signalings/** filters: - RemoveRequestHeader=Cookie - - RewritePath=/signalings/(?.*), /$\{segment} + - RewritePath=/signalings/(?.*), /$\{segment} default-filters: - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/CorsConfig.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/CorsConfig.java index c7bc0d3b..31efd89b 100644 --- a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/CorsConfig.java +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/CorsConfig.java @@ -1,5 +1,7 @@ package com.asyncgate.guild_server.config; +import java.util.List; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; @@ -15,7 +17,7 @@ public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOrigin("http://localhost:5173"); + config.setAllowedOriginPatterns(List.of("http://localhost:5173", "https://localhost:5173")); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.addExposedHeader("Authorization"); diff --git a/src/backend/signaling-server/http/SignalingControllerHttpRequest.http b/src/backend/signaling-server/http/SignalingControllerHttpRequest.http new file mode 100644 index 00000000..d21a5e86 --- /dev/null +++ b/src/backend/signaling-server/http/SignalingControllerHttpRequest.http @@ -0,0 +1,65 @@ +### 0.0 health check +// @no-log +GET {{host_url}}/health +Authorization: Bearer {{access_token}} + +### 1.0 임시 회원가입 +// @no-log +POST {{host_url}}/sign-up +Content-Type: application/json + +{ + "email": "{{user.API_1_0_SIGNUP.email}}", + "password": "{{user.API_1_0_SIGNUP.password}}", + "name": "{{user.API_1_0_SIGNUP.name}}", + "nickname": "{{user.API_1_0_SIGNUP.nickname}}", + "birth": "{{user.API_1_0_SIGNUP.birth}}" +} + + +### 1.1 로그인 +// @no-log +POST {{host_url}}/sign-in +Content-Type: application/json + +{ + "email": "{{user.API_1_1_SIGNIN.email}}", + "password": "{{user.API_1_1_SIGNIN.password}}" +} + +> {% + client.global.set("access_token", response.body.result.access_token); +%} + + +### 1.2 인증코드 인증 +// @no-log +POST {{host_url}}/validation/authentication-code +Content-Type: application/json + +{ + "email": "{{user.API_1_2_AUTHENTICATION_CODE.email}}", + "authentication_code": "{{user.API_1_2_AUTHENTICATION_CODE.authentication_code}}" +} + +### 2,0 채널 생성 +// @no-log +POST {{host_url}}/room/create +Content-Type: application/json +Authorization: Bearer {{access_token}} + +{ + "room_id": "{{signaling.API_2_0_CREATE_CHAT_ROOM.chat_room_id}}" +} + +### 2.1 채널 참여 +// @no-log +POST {{host_url}}/room/{{signaling.API_2_0_CREATE_CHAT_ROOM.chat_room_id}}/join +Content-Type: application/json +Authorization: Bearer {{access_token}} + + +### 2.2 채널 참여 중인 유저 조회 +// @no-log +GET {{host_url}}/room/{{signaling.API_2_0_CREATE_CHAT_ROOM.chat_room_id}}/users +Authorization: Bearer {{access_token}} \ No newline at end of file diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/CorsConfig.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/CorsConfig.java index 7cee73b1..a6b4b5f4 100644 --- a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/CorsConfig.java +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/CorsConfig.java @@ -18,7 +18,7 @@ public static CorsConfigurationSource corsConfigurationSource() { //리소스를 허용 ArrayList allowedOriginPatterns = new ArrayList<>(); allowedOriginPatterns.add("*"); - configuration.setAllowedOrigins(allowedOriginPatterns); + configuration.setAllowedOriginPatterns(allowedOriginPatterns); //허용하는 HTTP METHOD ArrayList allowedHttpMethods = new ArrayList<>(); @@ -33,9 +33,6 @@ public static CorsConfigurationSource corsConfigurationSource() { configuration.setAllowedHeaders(Collections.singletonList("*")); // configuration.setAllowedHeaders(List.of(HttpHeaders.AUTHORIZATION, HttpHeaders.CONTENT_TYPE)); - //인증, 인가를 위한 credentials 를 TRUE로 설정 - configuration.setAllowCredentials(true); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/KurentoConfig.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/KurentoConfig.java index afb1ece3..8227a9f3 100644 --- a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/KurentoConfig.java +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/config/KurentoConfig.java @@ -1,5 +1,6 @@ package com.asyncgate.signaling_server.config; +import com.asyncgate.signaling_server.infrastructure.client.MemberServiceClient; import com.asyncgate.signaling_server.signaling.KurentoManager; import com.asyncgate.signaling_server.support.handler.KurentoHandler; import org.kurento.client.KurentoClient; @@ -24,8 +25,13 @@ public KurentoClient kurentoClient() { } @Bean - public KurentoManager kurentoManager(KurentoClient kurentoClient) { - return new KurentoManager(kurentoClient); + public MemberServiceClient memberServiceClient() { + return new MemberServiceClient(); + } + + @Bean + public KurentoManager kurentoManager(KurentoClient kurentoClient, MemberServiceClient memberServiceClient) { + return new KurentoManager(kurentoClient, memberServiceClient); } @Bean @@ -35,7 +41,8 @@ public KurentoHandler kurentoHandler(KurentoManager kurentoManager) { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(kurentoHandler(kurentoManager(kurentoClient())), "/signal").setAllowedOrigins("*"); + System.out.println("🚀 WebSocketHandlerRegistry 등록"); + registry.addHandler(kurentoHandler(kurentoManager(kurentoClient(), memberServiceClient())), "/signal").setAllowedOrigins("*"); } @Bean diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/domain/Member.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/domain/Member.java index 2e6b3b8d..d2d8e140 100644 --- a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/domain/Member.java +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/domain/Member.java @@ -9,7 +9,8 @@ public class Member implements Identifiable { private final String id; // 고유한 식별자 - private final String userId; // 사용자의 ID + private final String progileImageUrl; + private final String nickname; private String roomId; // 사용자가 속한 방 ID // 미디어 상태 @@ -18,20 +19,22 @@ public class Member implements Identifiable { private boolean isScreenSharingEnabled; @Builder - public Member(final String id, final String userId, final String roomId, final boolean isMicEnabled, final boolean isCameraEnabled, final boolean isScreenSharingEnabled) { + public Member(final String id, final String roomId, final String progileImageUrl, final String nickname, final boolean isMicEnabled, final boolean isCameraEnabled, final boolean isScreenSharingEnabled) { this.id = id; - this.userId = userId; this.roomId = roomId; + this.progileImageUrl = progileImageUrl; + this.nickname = nickname; this.isMicEnabled = isMicEnabled; this.isCameraEnabled = isCameraEnabled; this.isScreenSharingEnabled = isScreenSharingEnabled; } - public static Member create(final String id, final String userId, final String roomId) { + public static Member create(final String id, final String roomId, final String progileImageUrl, final String nickname) { return Member.builder() .id(id) - .userId(userId) .roomId(roomId) + .progileImageUrl(progileImageUrl) + .nickname(nickname) .isMicEnabled(false) .isCameraEnabled(false) .isScreenSharingEnabled(false) diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/entity/MemberEntity.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/entity/MemberEntity.java index 0b13979d..54b18686 100644 --- a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/entity/MemberEntity.java +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/entity/MemberEntity.java @@ -14,9 +14,11 @@ public class MemberEntity { @Id private String id; - private String userId; // 사용자 ID private String roomId; // 참가 중인 방 ID + private String profileImageUrl; + private String nickname; + private boolean isMicEnabled; private boolean isCameraEnabled; private boolean isScreenSharingEnabled; diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/client/MemberServiceClient.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/client/MemberServiceClient.java new file mode 100644 index 00000000..19ed12d0 --- /dev/null +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/client/MemberServiceClient.java @@ -0,0 +1,37 @@ +package com.asyncgate.signaling_server.infrastructure.client; + +import com.asyncgate.signaling_server.domain.Member; +import com.asyncgate.signaling_server.exception.FailType; +import com.asyncgate.signaling_server.exception.SignalingServerException; +import com.asyncgate.signaling_server.infrastructure.dto.response.ReadUserRoomProfileResponse; +import com.asyncgate.signaling_server.infrastructure.utility.HttpClientUtil; +import com.asyncgate.signaling_server.support.response.SuccessResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class MemberServiceClient { + + // @Value("${service.member.url}") + private String memberServiceUrl = "http://43.201.101.122"; + + public Member fetchMemberById(String userId, String roomId) { + Map queryParams = new HashMap<>(); + queryParams.put("userId", userId); + + SuccessResponse response = HttpClientUtil.get( + memberServiceUrl, "/room/profile", queryParams, SuccessResponse.class); + + if (response == null || response.getResult() == null) { + throw new SignalingServerException(FailType._MEMBER_NOT_FOUND); + } + + ReadUserRoomProfileResponse userProfile = response.getResult(); + return Member.create(userId, roomId, userProfile.getProfileImageUrl(), userProfile.getNickname()); + } +} \ No newline at end of file diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/dto/response/ReadUserRoomProfileResponse.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/dto/response/ReadUserRoomProfileResponse.java new file mode 100644 index 00000000..0036a6cc --- /dev/null +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/dto/response/ReadUserRoomProfileResponse.java @@ -0,0 +1,33 @@ +package com.asyncgate.signaling_server.infrastructure.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ReadUserRoomProfileResponse { + // 유저의 id, 프로필 사진, 닉네임을 반환 + @JsonProperty("id") + @NotBlank + private String id; + + @JsonProperty("profile_image_url") + @NotBlank + private String profileImageUrl; + + @JsonProperty("nickname") + @NotBlank + private String nickname; + + @Builder + public ReadUserRoomProfileResponse( + String id, + String profileImageUrl, + String nickname + ) { + this.id = id; + this.profileImageUrl = profileImageUrl; + this.nickname = nickname; + } +} \ No newline at end of file diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/utility/HttpClientUtil.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/utility/HttpClientUtil.java new file mode 100644 index 00000000..67620ae6 --- /dev/null +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/infrastructure/utility/HttpClientUtil.java @@ -0,0 +1,33 @@ +package com.asyncgate.signaling_server.infrastructure.utility; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Map; + +public class HttpClientUtil { + private static final RestTemplate restTemplate = new RestTemplate(); + + // GET 요청 (쿼리 파라미터 지원) + public static T get(String baseUrl, String path, Map queryParams, Class responseType) { + String url = UriComponentsBuilder.fromUriString(baseUrl + path) + .queryParams(toMultiValueMap(queryParams)) + .toUriString(); + + return restTemplate.getForObject(url, responseType); + } + + // POST 요청 (Request Body 포함) + public static T post(String baseUrl, String path, R requestBody, Class responseType) { + String url = baseUrl + path; + return restTemplate.postForObject(url, requestBody, responseType); + } + + public static MultiValueMap toMultiValueMap(Map map) { + MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); + map.forEach(multiValueMap::add); + return multiValueMap; + } +} diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/signaling/KurentoManager.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/signaling/KurentoManager.java index 0846c1ad..6fc8dba7 100644 --- a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/signaling/KurentoManager.java +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/signaling/KurentoManager.java @@ -2,10 +2,12 @@ import com.asyncgate.signaling_server.domain.Member; import com.asyncgate.signaling_server.dto.response.GetUsersInChannelResponse; -import com.asyncgate.signaling_server.entity.MemberEntity; -import com.asyncgate.signaling_server.support.utility.DomainUtil; +import com.asyncgate.signaling_server.exception.FailType; +import com.asyncgate.signaling_server.exception.SignalingServerException; +import com.asyncgate.signaling_server.infrastructure.client.MemberServiceClient; import com.google.gson.Gson; import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; import org.kurento.client.*; import org.springframework.stereotype.Service; @@ -20,17 +22,20 @@ @Slf4j @Service +@RequiredArgsConstructor public class KurentoManager { private final KurentoClient kurentoClient; + private final MemberServiceClient memberServiceClient; + // kurento media pipline (SFU) 방에 대한 데이터 (key, value) private final Map pipelines = new ConcurrentHashMap<>(); private final Map> roomEndpoints = new ConcurrentHashMap<>(); - private final Map userStates = new ConcurrentHashMap<>(); - public KurentoManager(KurentoClient kurentoClient) { - this.kurentoClient = kurentoClient; - } + // 화면 공유용 WebRTC 엔드포인트 저장 (roomId -> userId -> WebRtcEndpoint) + private final Map> roomScreenEndpoints = new ConcurrentHashMap<>(); + + private final Map userStates = new ConcurrentHashMap<>(); /** * 특정 방에 대한 MediaPipeline을 가져오거나 새로 생성 @@ -48,29 +53,54 @@ public synchronized WebRtcEndpoint createEndpoint(String roomId, String userId) MediaPipeline pipeline = getOrCreatePipeline(roomId); WebRtcEndpoint endpoint = new WebRtcEndpoint.Builder(pipeline).build(); + // 사용자 정보 조회 + Member member = memberServiceClient.fetchMemberById(userId, roomId); + // ICE Candidate 리스너 추가 endpoint.addIceCandidateFoundListener(event -> { JsonObject candidateMessage = new JsonObject(); candidateMessage.addProperty("id", "iceCandidate"); - candidateMessage.addProperty("userId", userId); + candidateMessage.addProperty("userId", member.getId()); candidateMessage.add("candidate", new Gson().toJsonTree(event.getCandidate())); - log.info("🧊 [Kurento] ICE Candidate 전송: roomId={}, userId={}, candidate={}", roomId, userId, event.getCandidate()); + log.info("🧊 [Kurento] ICE Candidate 전송: roomId={}, userId={}, candidate={}", roomId, member.getId(), event.getCandidate()); }); // 사용자 엔드포인트 저장 roomEndpoints.computeIfAbsent(roomId, k -> new ConcurrentHashMap<>()).put(userId, endpoint); - userStates.put(userId, DomainUtil.MemberMapper.toDomain( - MemberEntity.builder() - .userId(userId) - .roomId(roomId) - .build()) - ); + userStates.put(userId, member); log.info("[Kurento] WebRTC Endpoint 생성: roomId={}, userId={}", roomId, userId); return endpoint; } + /** + * 화면 공유용 WebRTC 엔드포인트 생성 + * + * @param roomId + * @param userId + * @return + */ + public synchronized WebRtcEndpoint createScreenShareEndpoint(String roomId, String userId) { + MediaPipeline pipeline = getOrCreatePipeline(roomId); + WebRtcEndpoint screenEndpoint = new WebRtcEndpoint.Builder(pipeline).build(); + + // ICE Candidate 리스너 추가 + screenEndpoint.addIceCandidateFoundListener(event -> { + JsonObject candidateMessage = new JsonObject(); + candidateMessage.addProperty("id", "iceCandidate"); + candidateMessage.addProperty("userId", userId); + candidateMessage.add("candidate", new Gson().toJsonTree(event.getCandidate())); + + log.info("🖥️ [Kurento] 화면 공유 ICE Candidate 전송: roomId={}, userId={}, candidate={}", roomId, userId, event.getCandidate()); + }); + + // 화면 공유 엔드포인트 저장 + roomScreenEndpoints.computeIfAbsent(roomId, k -> new ConcurrentHashMap<>()).put(userId, screenEndpoint); + log.info("🖥️ [Kurento] 화면 공유 WebRTC Endpoint 생성: roomId={}, userId={}", roomId, userId); + return screenEndpoint; + } + /** * SDP Offer를 처리하고 Answer를 반환 */ @@ -111,13 +141,21 @@ public List getUsersInChannel(String chann String userId = entry.getKey(); WebRtcEndpoint endpoint = entry.getValue(); + + Member member = userStates.get(userId); + + // member가 null이라면 exception + if (member == null) { + throw new SignalingServerException(FailType._MEMBER_NOT_FOUND); + } + boolean isMicEnabled = endpoint.isMediaFlowingIn(MediaType.AUDIO) && endpoint.isMediaFlowingOut(MediaType.AUDIO); boolean isCameraEnabled = endpoint.isMediaFlowingIn(MediaType.VIDEO) && endpoint.isMediaFlowingOut(MediaType.VIDEO); return GetUsersInChannelResponse.UserInRoom.builder() - .id(userId) - .nickname(userId) // 닉네임 정보가 없으면 기본 userId 사용 - .profileImage("") // 프로필 이미지 필드가 없으면 기본 값 설정 + .id(member.getId()) + .nickname(member.getNickname()) // 닉네임 정보가 없으면 기본 userId 사용 + .profileImage(member.getProgileImageUrl()) // 프로필 이미지 필드가 없으면 기본 값 설정 .isMicEnabled(isMicEnabled) .isCameraEnabled(isCameraEnabled) .isScreenSharingEnabled(false) @@ -154,7 +192,7 @@ public void updateUserMediaState(String roomId, String userId, String type, bool } switch (type) { - case "audio": + case "mic": if (enabled) { reconnectAudio(userId, endpoint); } else { @@ -164,7 +202,7 @@ public void updateUserMediaState(String roomId, String userId, String type, bool member.updateMediaState("mic", enabled); break; - case "video": + case "camera": if (enabled) { reconnectVideo(userId, endpoint); } else { @@ -232,6 +270,32 @@ private void reconnectVideo(String userId, WebRtcEndpoint endpoint) { } } + /** + * 특정 사용자의 화면 공유 스트림 다시 연결 + */ + private void reconnectScreenShare(String userId, WebRtcEndpoint endpoint) { + endpoint.connect(endpoint, MediaType.VIDEO); + log.info("🖥️ [Kurento] 화면 공유 활성화: userId={}", userId); + + // ✅ userStates에서 해당 사용자의 상태 업데이트 + if (userStates.containsKey(userId)) { + userStates.get(userId).updateMediaState("screenShare", true); + } + } + + /** + * 특정 사용자의 화면 공유 스트림 연결 해제 + */ + private void disconnectScreenShare(String userId, WebRtcEndpoint endpoint) { + endpoint.disconnect(endpoint, MediaType.VIDEO); + log.info("🚫 [Kurento] 화면 공유 비활성화: userId={}", userId); + + // ✅ userStates에서 해당 사용자의 상태 업데이트 + if (userStates.containsKey(userId)) { + userStates.get(userId).updateMediaState("screenShare", false); + } + } + /** * 방을 제거함 */ @@ -241,9 +305,16 @@ public void removeRoom(String roomId) { roomEndpoints.remove(roomId); } + if (roomScreenEndpoints.containsKey(roomId)) { + roomScreenEndpoints.get(roomId).values().forEach(WebRtcEndpoint::release); + roomScreenEndpoints.remove(roomId); + } + if (pipelines.containsKey(roomId)) { pipelines.get(roomId).release(); pipelines.remove(roomId); } + + log.info("🛑 [Kurento] 방 제거 완료: roomId={}", roomId); } } \ No newline at end of file diff --git a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/support/utility/DomainUtil.java b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/support/utility/DomainUtil.java index 5d3e0d3d..e6386bcf 100644 --- a/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/support/utility/DomainUtil.java +++ b/src/backend/signaling-server/src/main/java/com/asyncgate/signaling_server/support/utility/DomainUtil.java @@ -16,7 +16,6 @@ public static class MemberMapper { public static MemberEntity toEntity(final Member member) { return MemberEntity.builder() .id(member.getId()) - .userId(member.getUserId()) .roomId(member.getRoomId()) .isMicEnabled(member.isMicEnabled()) .isCameraEnabled(member.isCameraEnabled()) @@ -27,7 +26,6 @@ public static MemberEntity toEntity(final Member member) { public static Member toDomain(final MemberEntity entity) { return Member.builder() .id(entity.getId()) - .userId(entity.getUserId()) .roomId(entity.getRoomId()) .isMicEnabled(entity.isMicEnabled()) .isCameraEnabled(entity.isCameraEnabled()) diff --git a/src/backend/signaling-server/src/main/resources/application-local.yml b/src/backend/signaling-server/src/main/resources/application-local.yml index 3d163681..53d67df4 100644 --- a/src/backend/signaling-server/src/main/resources/application-local.yml +++ b/src/backend/signaling-server/src/main/resources/application-local.yml @@ -1,5 +1,5 @@ server: - port: 0 + port: 8600 eureka: instance: diff --git a/src/backend/signaling-server/src/main/resources/application-prod.yml b/src/backend/signaling-server/src/main/resources/application-prod.yml index 3d163681..53d67df4 100644 --- a/src/backend/signaling-server/src/main/resources/application-prod.yml +++ b/src/backend/signaling-server/src/main/resources/application-prod.yml @@ -1,5 +1,5 @@ server: - port: 0 + port: 8600 eureka: instance: diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/config/CorsConfig.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/config/CorsConfig.java index 4dd48cba..f9f9c01c 100644 --- a/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/config/CorsConfig.java +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/config/CorsConfig.java @@ -6,6 +6,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; +import java.util.List; + @Configuration public class CorsConfig { @@ -15,7 +17,7 @@ public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOrigin("http://localhost:5173"); + config.setAllowedOriginPatterns(List.of("http://localhost:5173", "https://localhost:5173")); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.addExposedHeader("Authorization");