Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fbb8809
chore/#40 :: 불필요한 주석 제거
youn9k Nov 21, 2024
689bd16
Merge branch 'feat/#40-create-room' into feat/#40-create-room-2
youn9k Nov 21, 2024
dff1bad
feat/#40 :: 인코딩, 디코딩에 필요한 모델 구현
Kiyoung-Kim-57 Nov 21, 2024
7b667f7
chore/#40 :: 불필요한 주석 제거
Kiyoung-Kim-57 Nov 21, 2024
3b2c3af
refactor/#40 :: Room, RoomManager를 Struct -> Class 전환
Kiyoung-Kim-57 Nov 21, 2024
31e8f3c
feat/#40 :: 테스트용 라우트 코드 작성
Kiyoung-Kim-57 Nov 21, 2024
fec092f
feat/#40 :: Request의 타입을 디코딩 할 수 있는 구조체 구현
Kiyoung-Kim-57 Nov 21, 2024
99beada
style/#40 :: SignalingRequestDTO, RoomRequestDTO 생성자 수정
Kiyoung-Kim-57 Nov 21, 2024
527eba8
feat/#40 :: SDP, Candidate를 SignalingRequestDTO로 래핑해서 전달
Kiyoung-Kim-57 Nov 21, 2024
cde62d6
chore/#40 :: 불필요한 의존성 제거
youn9k Nov 21, 2024
91ec65e
feat/#40 :: SendOfferUseCase 구현
youn9k Nov 21, 2024
65d12c6
fix/#40 :: 전송할 데이터가 잘못 지정된 문제 수정
youn9k Nov 21, 2024
0a84adb
style/#40 :: 코드 가독성 향상
youn9k Nov 21, 2024
5dbff7d
fix/#40 :: 접근제어자 수정
youn9k Nov 21, 2024
2a68012
feat/#40 :: Local, Remote Video 가져오는 UseCase 생성
Kiyoung-Kim-57 Nov 21, 2024
bd471de
feat/#40 :: SendOfferUseCase WaitingRoomViewModel에 적용
Kiyoung-Kim-57 Nov 21, 2024
e50af8e
refactor/#40 :: SendOfferUseCase 생성자 접근제어자 수정
Kiyoung-Kim-57 Nov 21, 2024
ee7beb5
feat/#40 :: WaitingRoomFeatureDemo의 SceneDelegate 수정
Kiyoung-Kim-57 Nov 21, 2024
67779f0
feat/#40 :: 컬렉션뷰 DiffableDataSource 업데이트 테스트
youn9k Nov 21, 2024
67e8363
fix/#40 :: 디코딩 에러 수정
Kiyoung-Kim-57 Nov 21, 2024
14b0772
fix/#40 :: SignalingRequestDTO 생성자에서 할당 안해주던 오류 수정
Kiyoung-Kim-57 Nov 21, 2024
3cf8412
fix/#40 :: 서버 에러 수정
Kiyoung-Kim-57 Nov 21, 2024
cfca505
fix/#40 :: RoomRequestDTO 생성자 오류 수정
Kiyoung-Kim-57 Nov 21, 2024
7a27b0a
fix/#40 :: 서버 DTO 생성자 오류 수정
Kiyoung-Kim-57 Nov 21, 2024
ba4a83c
feat/#40 :: local, remote 화면 바인딩
youn9k Nov 21, 2024
fc7e04b
feat/#40 :: 1대1 화면 연결 완료
youn9k Nov 21, 2024
512a3a6
feat/#40 :: 비디오 캡쳐 기능 추가
Kiyoung-Kim-57 Nov 21, 2024
38d2df7
chore/#40 :: 클래스명 수정
youn9k Nov 23, 2024
30dcc7f
chore/#40 :: 클래스명 수정
youn9k Nov 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct RoomRequestDTO: WebSocketRequestable {
var messageType: RoomMessageType
var message: Data?

init(messageType: RoomMessageType, body: Data? = nil) {
init(messageType: RoomMessageType, message: Data? = nil) {
self.messageType = messageType
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct SignalingRequestDTO: WebSocketRequestable {
var messageType: SignalingMessageType
var message: Data?

init(messageType: SignalingMessageType = .signaling, body: Data? = nil) {
init(messageType: SignalingMessageType = .signaling, message: Data? = nil) {
self.messageType = messageType
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public final class RoomServiceImpl: RoomService {
private let encoder = JSONEncoder()
private var webSocketClient: WebSocketClient

init(webSocketClient: WebSocketClient) {
public init(webSocketClient: WebSocketClient) {
self.webSocketClient = webSocketClient
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ final public class SignalingServiceImpl: SignalingService {
let message = SignalingMessage.sdp(SessionDescription(from: rtcSdp, peerID: peerID, roomID: roomID))
do {
let dataMessage = try self.encoder.encode(message)
self.webSocketClient.send(data: dataMessage)
let dto = SignalingRequestDTO(messageType: .signaling, message: dataMessage)
let request = try self.encoder.encode(dto)

self.webSocketClient.send(data: request)
} catch {
debugPrint("Warning: Could not encode sdp: \(error)")
}
Expand All @@ -31,7 +34,10 @@ final public class SignalingServiceImpl: SignalingService {
let message = SignalingMessage.candidate(IceCandidate(from: rtcIceCandidate, peerID: peerID, roomID: roomID))
do {
let dataMessage = try self.encoder.encode(message)
self.webSocketClient.send(data: dataMessage)
let dto = SignalingRequestDTO(messageType: .signaling, message: dataMessage)
let request = try self.encoder.encode(dto)

self.webSocketClient.send(data: request)
} catch {
debugPrint("Warning: Could not encode candidate: \(error)")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import UIKit
import PhotoGetherDomainInterface

public final class GetLocalVideoUseCaseImpl: GetLocalVideoUseCase {
public func execute() -> UIView {
connectionRepository.clients[0].localVideoView
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 Repository에서 하나의 localVideoView만 들고있도록 할 예정입니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 Client 프로퍼티에서 host/guest 여부와 닉네임을 알 수 있도록 하면 좋을 것 같습니다!

}

private let connectionRepository: ConnectionRepository

public init(connectionRepository: ConnectionRepository) {
self.connectionRepository = connectionRepository
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import UIKit
import PhotoGetherDomainInterface

public final class GetRemoteVideoUseCaseImpl: GetRemoteVideoUseCase {
public func execute() -> [UIView] {
connectionRepository.clients.map { $0.remoteVideoView }
}

private let connectionRepository: ConnectionRepository

public init(connectionRepository: ConnectionRepository) {
self.connectionRepository = connectionRepository
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation
import PhotoGetherDomainInterface

public final class SendOfferUseCaseImpl: SendOfferUseCase {
public func execute() {
// TODO: 특정 Peer에게만 Offer를 보내도록 수정해야 함
guard let client = repository.clients.first else { return }
client.sendOffer()
}

private let repository: ConnectionRepository

init(repository: ConnectionRepository) {
self.repository = repository
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation
import UIKit

public protocol GetLocalVideoUseCase {
func execute() -> UIView
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation
import UIKit

public protocol GetRemoteVideoUseCase {
func execute() -> [UIView]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

public protocol SendOfferUseCase {
func execute()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
6075676A2CEDA2E6001110DB /* PhotoGetherData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607567692CEDA2E6001110DB /* PhotoGetherData.framework */; };
6075676B2CEDA2E6001110DB /* PhotoGetherData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607567692CEDA2E6001110DB /* PhotoGetherData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7B17455F2CE49EB100E01D1A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = 7B17455E2CE49EB100E01D1A /* WebRTC */; };
7B5951062CDB597600B89C85 /* FeatureTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5951052CDB597600B89C85 /* FeatureTesting.framework */; };
7B5951072CDB597600B89C85 /* FeatureTesting.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5951052CDB597600B89C85 /* FeatureTesting.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7B5951462CDB5FF200B89C85 /* BaseFeature.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5951452CDB5FF200B89C85 /* BaseFeature.framework */; };
7B5951472CDB5FF200B89C85 /* BaseFeature.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5951452CDB5FF200B89C85 /* BaseFeature.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7B5951C92CDB656D00B89C85 /* PhotoGetherDomainInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5951C82CDB656D00B89C85 /* PhotoGetherDomainInterface.framework */; };
Expand All @@ -45,7 +43,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
7B5951072CDB597600B89C85 /* FeatureTesting.framework in Embed Frameworks */,
6013799E2CDB4B4500CA430A /* WaitingRoomFeature.framework in Embed Frameworks */,
60348F302CE48F02002D1CEE /* PhotoGetherDomain.framework in Embed Frameworks */,
6075676B2CEDA2E6001110DB /* PhotoGetherData.framework in Embed Frameworks */,
Expand Down Expand Up @@ -129,7 +126,6 @@
files = (
6075676A2CEDA2E6001110DB /* PhotoGetherData.framework in Frameworks */,
7B17455F2CE49EB100E01D1A /* WebRTC in Frameworks */,
7B5951062CDB597600B89C85 /* FeatureTesting.framework in Frameworks */,
6013799D2CDB4B4500CA430A /* WaitingRoomFeature.framework in Frameworks */,
60348F2F2CE48F02002D1CEE /* PhotoGetherDomain.framework in Frameworks */,
60348F342CE49013002D1CEE /* PhotoGetherNetwork.framework in Frameworks */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import PhotoGetherDomainInterface
import PhotoGetherData
import UIKit
import PhotoGetherNetwork
import PhotoGetherData
import PhotoGetherDomainInterface
import PhotoGetherDomain
import WaitingRoomFeature
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
Expand All @@ -18,15 +19,24 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
debugPrint("SignalingServer URL: \(url)")

let webScoketClient: WebSocketClient = WebSocketClientImpl(url: url)
let signalingService: SignalingService = SignalingServiceImpl(webSocketClient: webScoketClient)

let webRTCService: WebRTCService = WebRTCServiceImpl(iceServers: [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302"
])

let roomService: RoomService = RoomServiceImpl(
webSocketClient: webScoketClient
)

let signalingService: SignalingService = SignalingServiceImpl(
webSocketClient: webScoketClient
)

let webRTCService: WebRTCService = WebRTCServiceImpl(
iceServers: [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302"
]
)

let connectionClient: ConnectionClient = ConnectionClientImpl(
signalingService: signalingService,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

struct RoomRequestDTO: WebSocketRequestable {
var messageType: RoomMessageType
var message: Data?

init(messageType: RoomMessageType, body: Data? = nil) {
self.messageType = messageType
}

enum RoomMessageType: String, Decodable {
case createRoom
case joinRoom
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

struct SignalingRequestDTO: WebSocketRequestable {
var messageType: SignalingMessageType
var message: Data?

init(messageType: SignalingMessageType = .signaling, body: Data? = nil) {
self.messageType = messageType
}

enum SignalingMessageType: String, Decodable {
case signaling
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

struct WebSocketRequestType: Decodable {
let type: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

struct RoomResponseDTO: WebSocketRequestable {
var messageType: RoomMessageType
var message: Data?

enum RoomMessageType: String, Decodable {
case createRoom
case joinRoom
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
struct Room {
final class Room {
private var users: [User] = []
private var maxCount: Int = 4
let roomID: String


init(roomID: String) {
self.roomID = roomID
}

@discardableResult
mutating func invite(user: User) -> Bool {
func invite(user: User) -> Bool {
guard users.count < maxCount else { return false }
users.append(user)
return true
}

@discardableResult
mutating func kick(userID: String) -> Bool {
func kick(userID: String) -> Bool {
let filtered = users.filter { $0.id != userID }
users = filtered
return filtered.isEmpty // 필터에 걸렸으면 찾은 것
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Vapor

struct RoomManager {
final class RoomManager {
private var rooms: [Room] = []

mutating func createRoom(_ client: WebSocket) -> (roomID: String, userID: String) {
func createRoom(_ client: WebSocket) -> (roomID: String, userID: String) {
let roomID = randomRoomID()
let userID = randomUserID()

Expand All @@ -16,7 +16,6 @@ struct RoomManager {
return (roomID, userID)
}


private func randomRoomID() -> String {
return "room-\(UUID().uuidString)"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

public protocol WebSocketRequestable: Decodable {
associatedtype ResponseType: Decodable
var messageType: ResponseType { get }
var message: Data? { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

public protocol WebSocketResponsable: Encodable {
associatedtype RequestType: Encodable
var messageType: RequestType { get }
var message: Data? { get }

func toData(encoder: JSONEncoder) -> Data?
}

public extension WebSocketResponsable {
func toData(encoder: JSONEncoder) -> Data? {
return try? encoder.encode(self)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import Vapor

// configures your application
public func configure(_ app: Application) async throws {
// uncomment to serve files from /Public folder
// app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
// register routes
app.http.server.configuration.hostname = "0.0.0.0"
app.http.server.configuration.port = 8080
try routes(app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@ enum Entrypoint {
try LoggingSystem.bootstrap(from: &env)

let app = try await Application.make(env)

// This attempts to install NIO as the Swift Concurrency global executor.
// You can enable it if you'd like to reduce the amount of context switching between NIO and Swift Concurrency.
// Note: this has caused issues with some libraries that use `.wait()` and cleanly shutting down.
// If enabled, you should be careful about calling async functions before this point as it can cause assertion failures.
// let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
// app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)])

do {
try await configure(app)
Expand Down
35 changes: 30 additions & 5 deletions PhotoGetherServer/PhotoGetherServer/Sources/App/routes.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Foundation
import Vapor

var roomManager = RoomManager()
let decoder = JSONDecoder()
let encoder = JSONEncoder()

func routes(_ app: Application) throws {
var connectedClients = [WebSocket]()
Expand All @@ -14,14 +17,36 @@ func routes(_ app: Application) throws {

// 클라이언트로부터 데이터를 수신할 때 호출
client.onBinary { client, data in

print("Received binary data of size: \(data.readableBytes)")
// TODO: 1. data -> JSON으로 디코딩
guard let requestType = try? decoder.decode(WebSocketRequestType.self, from: data) else {
print("Decode Failed to WebSocketRequestType: \(data)")
return
}

switch requestType.type {
case "signaling":
guard let request = try? decoder.decode(SignalingRequestDTO.self, from: data) else {
print("Decode Failed to SignalingRequestDTO: \(data)")
return
}

guard let data = request.message else {
print("Message is Nil")
return
}

connectedClients
.filter { $0 !== client }
.forEach { $0.send(data) }
default:
print("Unknown request message type: \(requestType.type)")
}
// TODO: 2. type을 보고 수행할 명령을 선택

print("Received binary data of size: \(data.readableBytes)")
connectedClients
.filter { $0 !== client }
.forEach { $0.send(data) }
// connectedClients
// .filter { $0 !== client }
// .forEach { $0.send(data) }
}

// 클라이언트가 연결을 종료할 때 호출
Expand Down