diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ea37134 Binary files /dev/null and b/.DS_Store differ diff --git a/iOS/Ringo/.DS_Store b/iOS/Ringo/.DS_Store index 81ead56..1a9be97 100644 Binary files a/iOS/Ringo/.DS_Store and b/iOS/Ringo/.DS_Store differ diff --git a/iOS/Ringo/Ringo.xcodeproj/project.pbxproj b/iOS/Ringo/Ringo.xcodeproj/project.pbxproj index 1f40c9c..0700257 100644 --- a/iOS/Ringo/Ringo.xcodeproj/project.pbxproj +++ b/iOS/Ringo/Ringo.xcodeproj/project.pbxproj @@ -9,6 +9,17 @@ /* Begin PBXBuildFile section */ 940C25642B73D20900E069D0 /* ConnectionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25632B73D20900E069D0 /* ConnectionCollectionViewCell.swift */; }; 940C25662B74EF4D00E069D0 /* FriendService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25652B74EF4D00E069D0 /* FriendService.swift */; }; + 940C25722B7A88FB00E069D0 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25712B7A88FB00E069D0 /* Config.swift */; }; + 940C25782B7A899500E069D0 /* RTCStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25752B7A899500E069D0 /* RTCStates.swift */; }; + 940C25792B7A899500E069D0 /* SessionDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25762B7A899500E069D0 /* SessionDescription.swift */; }; + 940C257A2B7A899500E069D0 /* IceCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25772B7A899500E069D0 /* IceCandidate.swift */; }; + 940C25832B7A89AB00E069D0 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C257C2B7A89AB00E069D0 /* Message.swift */; }; + 940C25842B7A89AB00E069D0 /* WebSocketProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C257E2B7A89AB00E069D0 /* WebSocketProvider.swift */; }; + 940C25852B7A89AB00E069D0 /* NativeWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C257F2B7A89AB00E069D0 /* NativeWebSocket.swift */; }; + 940C25862B7A89AB00E069D0 /* StarscreamProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25802B7A89AB00E069D0 /* StarscreamProvider.swift */; }; + 940C25872B7A89AB00E069D0 /* WebRTCClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25812B7A89AB00E069D0 /* WebRTCClient.swift */; }; + 940C25882B7A89AB00E069D0 /* SignalingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25822B7A89AB00E069D0 /* SignalingClient.swift */; }; + 940C258A2B7A8FA900E069D0 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25892B7A8FA900E069D0 /* CallService.swift */; }; 94470A8A2B71050700F0A942 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 94470A892B71050700F0A942 /* Alamofire */; }; 94470A8D2B710A3100F0A942 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 94470A8C2B710A3100F0A942 /* Starscream */; }; 94470A902B710CE500F0A942 /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = 94470A8F2B710CE500F0A942 /* WebRTC */; }; @@ -33,6 +44,17 @@ /* Begin PBXFileReference section */ 940C25632B73D20900E069D0 /* ConnectionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionCollectionViewCell.swift; sourceTree = ""; }; 940C25652B74EF4D00E069D0 /* FriendService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendService.swift; sourceTree = ""; }; + 940C25712B7A88FB00E069D0 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 940C25752B7A899500E069D0 /* RTCStates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTCStates.swift; sourceTree = ""; }; + 940C25762B7A899500E069D0 /* SessionDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionDescription.swift; sourceTree = ""; }; + 940C25772B7A899500E069D0 /* IceCandidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IceCandidate.swift; sourceTree = ""; }; + 940C257C2B7A89AB00E069D0 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + 940C257E2B7A89AB00E069D0 /* WebSocketProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketProvider.swift; sourceTree = ""; }; + 940C257F2B7A89AB00E069D0 /* NativeWebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWebSocket.swift; sourceTree = ""; }; + 940C25802B7A89AB00E069D0 /* StarscreamProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarscreamProvider.swift; sourceTree = ""; }; + 940C25812B7A89AB00E069D0 /* WebRTCClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebRTCClient.swift; sourceTree = ""; }; + 940C25822B7A89AB00E069D0 /* SignalingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalingClient.swift; sourceTree = ""; }; + 940C25892B7A8FA900E069D0 /* CallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallService.swift; sourceTree = ""; }; 94470A922B7163E900F0A942 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 94470A942B71680100F0A942 /* SigninService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigninService.swift; sourceTree = ""; }; 94470A962B717DC300F0A942 /* ConnectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionViewController.swift; sourceTree = ""; }; @@ -67,6 +89,48 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 940C256A2B79EC5700E069D0 /* WebRTC */ = { + isa = PBXGroup; + children = ( + 940C257B2B7A89AB00E069D0 /* Services */, + 940C25742B7A899500E069D0 /* Extensions */, + 940C25712B7A88FB00E069D0 /* Config.swift */, + 940C25892B7A8FA900E069D0 /* CallService.swift */, + ); + path = WebRTC; + sourceTree = ""; + }; + 940C25742B7A899500E069D0 /* Extensions */ = { + isa = PBXGroup; + children = ( + 940C25752B7A899500E069D0 /* RTCStates.swift */, + 940C25762B7A899500E069D0 /* SessionDescription.swift */, + 940C25772B7A899500E069D0 /* IceCandidate.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 940C257B2B7A89AB00E069D0 /* Services */ = { + isa = PBXGroup; + children = ( + 940C257C2B7A89AB00E069D0 /* Message.swift */, + 940C257D2B7A89AB00E069D0 /* WebSocketProvider */, + 940C25812B7A89AB00E069D0 /* WebRTCClient.swift */, + 940C25822B7A89AB00E069D0 /* SignalingClient.swift */, + ); + path = Services; + sourceTree = ""; + }; + 940C257D2B7A89AB00E069D0 /* WebSocketProvider */ = { + isa = PBXGroup; + children = ( + 940C257E2B7A89AB00E069D0 /* WebSocketProvider.swift */, + 940C257F2B7A89AB00E069D0 /* NativeWebSocket.swift */, + 940C25802B7A89AB00E069D0 /* StarscreamProvider.swift */, + ); + path = WebSocketProvider; + sourceTree = ""; + }; 94470A912B71157C00F0A942 /* Screens */ = { isa = PBXGroup; children = ( @@ -99,6 +163,7 @@ 945CF9792B67E1CD00396E4E /* Ringo */ = { isa = PBXGroup; children = ( + 940C256A2B79EC5700E069D0 /* WebRTC */, 94470A912B71157C00F0A942 /* Screens */, 945CF97A2B67E1CD00396E4E /* AppDelegate.swift */, 945CF97C2B67E1CD00396E4E /* SceneDelegate.swift */, @@ -201,16 +266,27 @@ 945603212B6D2F06002F4B33 /* Canvas.swift in Sources */, 94470A972B717DC300F0A942 /* ConnectionViewController.swift in Sources */, 945603132B6AC07D002F4B33 /* TabBarViewController.swift in Sources */, + 940C25782B7A899500E069D0 /* RTCStates.swift in Sources */, + 940C25852B7A89AB00E069D0 /* NativeWebSocket.swift in Sources */, 945CF97F2B67E1CD00396E4E /* ViewController.swift in Sources */, 940C25642B73D20900E069D0 /* ConnectionCollectionViewCell.swift in Sources */, + 940C257A2B7A899500E069D0 /* IceCandidate.swift in Sources */, 945603192B6AC365002F4B33 /* AccountViewController.swift in Sources */, + 940C258A2B7A8FA900E069D0 /* CallService.swift in Sources */, 945CF97B2B67E1CD00396E4E /* AppDelegate.swift in Sources */, 945603172B6AC2D3002F4B33 /* RecentsViewController.swift in Sources */, + 940C25872B7A89AB00E069D0 /* WebRTCClient.swift in Sources */, 945603232B6D31D9002F4B33 /* ContactsTableViewCell.swift in Sources */, 945CF97D2B67E1CD00396E4E /* SceneDelegate.swift in Sources */, 94470A932B7163E900F0A942 /* Model.swift in Sources */, 940C25662B74EF4D00E069D0 /* FriendService.swift in Sources */, + 940C25792B7A899500E069D0 /* SessionDescription.swift in Sources */, 945603152B6AC22A002F4B33 /* ContactsViewController.swift in Sources */, + 940C25722B7A88FB00E069D0 /* Config.swift in Sources */, + 940C25842B7A89AB00E069D0 /* WebSocketProvider.swift in Sources */, + 940C25882B7A89AB00E069D0 /* SignalingClient.swift in Sources */, + 940C25832B7A89AB00E069D0 /* Message.swift in Sources */, + 940C25862B7A89AB00E069D0 /* StarscreamProvider.swift in Sources */, 9456031D2B6BF44A002F4B33 /* FriendRequestViewController.swift in Sources */, 94470A952B71680100F0A942 /* SigninService.swift in Sources */, ); diff --git a/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate index f738667..fd6d2c0 100644 Binary files a/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate and b/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iOS/Ringo/Ringo/.DS_Store b/iOS/Ringo/Ringo/.DS_Store index 5008ddf..642c9ec 100644 Binary files a/iOS/Ringo/Ringo/.DS_Store and b/iOS/Ringo/Ringo/.DS_Store differ diff --git a/iOS/Ringo/Ringo/ConnectionViewController.swift b/iOS/Ringo/Ringo/ConnectionViewController.swift index 94c9577..cd0b39d 100644 --- a/iOS/Ringo/Ringo/ConnectionViewController.swift +++ b/iOS/Ringo/Ringo/ConnectionViewController.swift @@ -21,7 +21,7 @@ class ConnectionViewController: UIViewController { let translateBtn = UIButton() let hangUpBtn = UIButton() - let randomNames = ["민준", "서준"] + let randomNames = ["example", "example2"] override func viewDidLoad() { super.viewDidLoad() diff --git a/iOS/Ringo/Ringo/ContactsTableViewCell.swift b/iOS/Ringo/Ringo/ContactsTableViewCell.swift index 7989650..42a027b 100644 --- a/iOS/Ringo/Ringo/ContactsTableViewCell.swift +++ b/iOS/Ringo/Ringo/ContactsTableViewCell.swift @@ -8,6 +8,10 @@ import UIKit import SnapKit +protocol ContactsTableViewCellDelegate: AnyObject { + func pressedButton() +} + class ContactsTableViewCell: UITableViewCell { static let identifier = "ContactsTableViewCell" @@ -21,6 +25,8 @@ class ContactsTableViewCell: UITableViewCell { let paddingView = UIView() let paddingView2 = UIView() + var delegate: ContactsTableViewCellDelegate? + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setUpView() @@ -53,6 +59,7 @@ class ContactsTableViewCell: UITableViewCell { call.configuration = .plain() call.configuration?.imagePadding = 10 call.configuration?.baseForegroundColor = .systemGreen + call.addTarget(self, action: #selector(callBtnAction), for: .touchUpInside) stackView.axis = .vertical stackView.alignment = .leading @@ -102,6 +109,10 @@ class ContactsTableViewCell: UITableViewCell { super.setSelected(selected, animated: animated) moreStackView.isHidden = !selected } + + @objc func callBtnAction(){ + delegate?.pressedButton() + } } // MARK: - stackview border diff --git a/iOS/Ringo/Ringo/Info.plist b/iOS/Ringo/Ringo/Info.plist index 002cb53..5569335 100644 --- a/iOS/Ringo/Ringo/Info.plist +++ b/iOS/Ringo/Ringo/Info.plist @@ -2,6 +2,8 @@ + NSMicrophoneUsageDescription + Need microphone access for use device's mic UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/iOS/Ringo/Ringo/Screens/ContactsViewController.swift b/iOS/Ringo/Ringo/Screens/ContactsViewController.swift index 16491bf..500b163 100644 --- a/iOS/Ringo/Ringo/Screens/ContactsViewController.swift +++ b/iOS/Ringo/Ringo/Screens/ContactsViewController.swift @@ -98,11 +98,12 @@ extension ContactsViewController: UITableViewDataSource { cell.name.text = randomNames[indexPath.row] cell.selectionStyle = .none + cell.delegate = self return cell } } -// MARK: - Sign in +// MARK: - Load Friends extension ContactsViewController { func loadFriends() { @@ -134,6 +135,19 @@ extension ContactsViewController { } } +// MARK: - Load Friends +extension ContactsViewController: ContactsTableViewCellDelegate { + + func pressedButton() { + CallService.shared.webRTCClient.offer { (sdp) in + CallService.shared.signalClient.send(sdp: sdp) + } + let connectionVC = ConnectionViewController() + connectionVC.modalPresentationStyle = .fullScreen + present(connectionVC, animated: true,completion: nil) + } + +} // MARK: - canvas 이용하기 import SwiftUI @available(iOS 13.0.0, *) diff --git a/iOS/Ringo/Ringo/Screens/TabBarViewController.swift b/iOS/Ringo/Ringo/Screens/TabBarViewController.swift index 989ee89..7b87d5f 100644 --- a/iOS/Ringo/Ringo/Screens/TabBarViewController.swift +++ b/iOS/Ringo/Ringo/Screens/TabBarViewController.swift @@ -7,12 +7,17 @@ import UIKit import SnapKit +import WebRTC class TabBarViewController: UITabBarController{ override func viewDidLoad() { super.viewDidLoad() + CallService.shared.webRTCClient.delegate = self + CallService.shared.signalClient.delegate = self + CallService.shared.signalClient.connect() + view.backgroundColor = .systemBackground tabBar.backgroundColor = .init(white: 1, alpha: 0.8) @@ -31,13 +36,73 @@ class TabBarViewController: UITabBarController{ } } -// MARK: canvas 이용하기 -import SwiftUI -@available(iOS 13.0.0, *) -struct TabBarViewControllerPreView: PreviewProvider { - static var previews: some View { - // 사용할 뷰 컨트롤러를 넣어주세요 - TabBarViewController() - .toPreview() +extension TabBarViewController: SignalClientDelegate { + func signalClientDidConnect(_ signalClient: SignalingClient) { + print("signal connect") + } + + func signalClientDidDisconnect(_ signalClient: SignalingClient) { + print("signal disconnect") + } + + func signalClient(_ signalClient: SignalingClient, didReceiveRemoteSdp sdp: RTCSessionDescription) { + print("Received remote sdp") + CallService.shared.webRTCClient.set(remoteSdp: sdp) { (error) in + DispatchQueue.main.async { + let alert = UIAlertController(title: "Call", message: "", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Refuse", style: .destructive, handler: nil)) + alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { action in + self.acceptCall() + })) + self.present(alert, animated: true, completion: nil) + } + } + } + + func signalClient(_ signalClient: SignalingClient, didReceiveCandidate candidate: RTCIceCandidate) { + CallService.shared.webRTCClient.set(remoteCandidate: candidate) { error in + print("Received remote candidate : \(candidate)") + } + } + + func acceptCall() { + CallService.shared.webRTCClient.answer { (localSdp) in + CallService.shared.signalClient.send(sdp: localSdp) + } + let connectionVC = ConnectionViewController() + connectionVC.modalPresentationStyle = .fullScreen + self.present(connectionVC, animated: true,completion: nil) + } + +} + +extension TabBarViewController: WebRTCClientDelegate { + func webRTCClient(_ client: WebRTCClient, didDiscoverLocalCandidate candidate: RTCIceCandidate) { + print("discovered local candidate") + CallService.shared.signalClient.send(candidate: candidate) + } + + func webRTCClient(_ client: WebRTCClient, didChangeConnectionState state: RTCIceConnectionState) { + print("change connection state : \(state)") + } + + func webRTCClient(_ client: WebRTCClient, didReceiveData data: Data) { + DispatchQueue.main.async { + let message = String(data: data, encoding: .utf8) ?? "(Binary: \(data.count) bytes)" + let alert = UIAlertController(title: "Message from WebRTC", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + } } } + +//// MARK: canvas 이용하기 +//import SwiftUI +//@available(iOS 13.0.0, *) +//struct TabBarViewControllerPreView: PreviewProvider { +// static var previews: some View { +// // 사용할 뷰 컨트롤러를 넣어주세요 +// TabBarViewController() +// .toPreview() +// } +//} diff --git a/iOS/Ringo/Ringo/Screens/ViewController.swift b/iOS/Ringo/Ringo/Screens/ViewController.swift index 8ddae8c..8b18636 100644 --- a/iOS/Ringo/Ringo/Screens/ViewController.swift +++ b/iOS/Ringo/Ringo/Screens/ViewController.swift @@ -85,6 +85,8 @@ class ViewController: UIViewController { input_email.layer.cornerRadius = 5 input_email.keyboardType = .emailAddress input_email.autocapitalizationType = .none + input_email.autocorrectionType = .no + input_email.spellCheckingType = .no stackView.axis = .vertical stackView.alignment = .fill @@ -116,6 +118,9 @@ class ViewController: UIViewController { input_passwd.rightView = showBtn input_passwd.rightViewMode = .always input_passwd.autocapitalizationType = .none + input_passwd.delegate = self + input_passwd.autocorrectionType = .no + input_passwd.spellCheckingType = .no error.layer.isHidden = true error.setTitle(" Incorrect password. Please check your password.", for: .normal) @@ -219,6 +224,11 @@ class ViewController: UIViewController { make.trailing.equalTo(apple.snp.leading).offset(-20) } } + // 빈 화면 터치 시 키보드 내리기 + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + view.endEditing(true) + } + @objc func onPressSignin(_ sender: UIButton) { login() } @@ -270,9 +280,7 @@ extension ViewController { // UserDefaults.standard.set(data.data?.jwtToken, forKey: "jwtToken") let nav = UINavigationController() nav.modalPresentationStyle = .fullScreen - nav.navigationBar.barTintColor = .white -// nav.navigationBar.tintColor = UIColor(w: 42) - + //네비게이션 중복 수정 1/31 nav.navigationBar.isHidden = true @@ -301,3 +309,10 @@ extension ViewController { } } } +// MARK: - canvas 이용하기 +extension ViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/.DS_Store b/iOS/Ringo/Ringo/WebRTC/.DS_Store new file mode 100644 index 0000000..9c56828 Binary files /dev/null and b/iOS/Ringo/Ringo/WebRTC/.DS_Store differ diff --git a/iOS/Ringo/Ringo/WebRTC/CallService.swift b/iOS/Ringo/Ringo/WebRTC/CallService.swift new file mode 100644 index 0000000..2934a7a --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/CallService.swift @@ -0,0 +1,22 @@ +// +// CallService.swift +// Ringo +// +// Created by 강진혁 on 2/13/24. +// + +import Foundation + +class CallService { + + static let shared = CallService() + + private let config = Config.default + var signalClient: SignalingClient + var webRTCClient: WebRTCClient + + init() { + self.signalClient = SignalingClient(webSocket: NativeWebSocket(url: self.config.signalingServerUrl)) + self.webRTCClient = WebRTCClient(iceServers: self.config.webRTCIceServers) + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Config.swift b/iOS/Ringo/Ringo/WebRTC/Config.swift new file mode 100644 index 0000000..f090e2f --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Config.swift @@ -0,0 +1,26 @@ +// +// Config.swift +// WebRTC-Demo +// +// Created by Stasel on 30/01/2019. +// Copyright © 2019 Stasel. All rights reserved. +// + +import Foundation + +// Set this to the machine's address which runs the signaling server. Do not use 'localhost' or '127.0.0.1' +fileprivate let defaultSignalingServerUrl = URL(string: "ws://192.168.0.8:8080")! + +// We use Google's public stun servers. For production apps you should deploy your own stun/turn servers. +fileprivate let defaultIceServers = ["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"] + +struct Config { + let signalingServerUrl: URL + let webRTCIceServers: [String] + + static let `default` = Config(signalingServerUrl: defaultSignalingServerUrl, webRTCIceServers: defaultIceServers) +} diff --git a/iOS/Ringo/Ringo/WebRTC/Extensions/IceCandidate.swift b/iOS/Ringo/Ringo/WebRTC/Extensions/IceCandidate.swift new file mode 100644 index 0000000..b66388d --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Extensions/IceCandidate.swift @@ -0,0 +1,27 @@ +// +// IceCandidate.swift +// WebRTC-Demo +// +// Created by Stasel on 20/02/2019. +// Copyright © 2019 Stasel. All rights reserved. +// + +import Foundation +import WebRTC + +/// This struct is a swift wrapper over `RTCIceCandidate` for easy encode and decode +struct IceCandidate: Codable { + let sdp: String + let sdpMLineIndex: Int32 + let sdpMid: String? + + init(from iceCandidate: RTCIceCandidate) { + self.sdpMLineIndex = iceCandidate.sdpMLineIndex + self.sdpMid = iceCandidate.sdpMid + self.sdp = iceCandidate.sdp + } + + var rtcIceCandidate: RTCIceCandidate { + return RTCIceCandidate(sdp: self.sdp, sdpMLineIndex: self.sdpMLineIndex, sdpMid: self.sdpMid) + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Extensions/RTCStates.swift b/iOS/Ringo/Ringo/WebRTC/Extensions/RTCStates.swift new file mode 100644 index 0000000..9c5302f --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Extensions/RTCStates.swift @@ -0,0 +1,64 @@ +// +// RTCConnectionState.swift +// WebRTC +// +// Created by Stasel on 20/05/2018. +// Copyright © 2018 Stasel. All rights reserved. +// + +import Foundation +import WebRTC + +extension RTCIceConnectionState: CustomStringConvertible { + public var description: String { + switch self { + case .new: return "new" + case .checking: return "checking" + case .connected: return "connected" + case .completed: return "completed" + case .failed: return "failed" + case .disconnected: return "disconnected" + case .closed: return "closed" + case .count: return "count" + @unknown default: return "Unknown \(self.rawValue)" + } + } +} + +extension RTCSignalingState: CustomStringConvertible { + public var description: String { + switch self { + case .stable: return "stable" + case .haveLocalOffer: return "haveLocalOffer" + case .haveLocalPrAnswer: return "haveLocalPrAnswer" + case .haveRemoteOffer: return "haveRemoteOffer" + case .haveRemotePrAnswer: return "haveRemotePrAnswer" + case .closed: return "closed" + @unknown default: return "Unknown \(self.rawValue)" + } + } +} + +extension RTCIceGatheringState: CustomStringConvertible { + public var description: String { + switch self { + case .new: return "new" + case .gathering: return "gathering" + case .complete: return "complete" + @unknown default: return "Unknown \(self.rawValue)" + } + } +} + +extension RTCDataChannelState: CustomStringConvertible { + public var description: String { + switch self { + case .connecting: return "connecting" + case .open: return "open" + case .closing: return "closing" + case .closed: return "closed" + @unknown default: return "Unknown \(self.rawValue)" + } + } +} + diff --git a/iOS/Ringo/Ringo/WebRTC/Extensions/SessionDescription.swift b/iOS/Ringo/Ringo/WebRTC/Extensions/SessionDescription.swift new file mode 100644 index 0000000..252cf33 --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Extensions/SessionDescription.swift @@ -0,0 +1,47 @@ +// +// SessionDescription.swift +// WebRTC-Demo +// +// Created by Stasel on 20/02/2019. +// Copyright © 2019 Stasel. All rights reserved. +// + +import Foundation +import WebRTC + +/// This enum is a swift wrapper over `RTCSdpType` for easy encode and decode +enum SdpType: String, Codable { + case offer, prAnswer, answer, rollback + + var rtcSdpType: RTCSdpType { + switch self { + case .offer: return .offer + case .answer: return .answer + case .prAnswer: return .prAnswer + case .rollback: return .rollback + } + } +} + +/// This struct is a swift wrapper over `RTCSessionDescription` for easy encode and decode +struct SessionDescription: Codable { + let sdp: String + let type: SdpType + + init(from rtcSessionDescription: RTCSessionDescription) { + self.sdp = rtcSessionDescription.sdp + + switch rtcSessionDescription.type { + case .offer: self.type = .offer + case .prAnswer: self.type = .prAnswer + case .answer: self.type = .answer + case .rollback: self.type = .rollback + @unknown default: + fatalError("Unknown RTCSessionDescription type: \(rtcSessionDescription.type.rawValue)") + } + } + + var rtcSessionDescription: RTCSessionDescription { + return RTCSessionDescription(type: self.type.rtcSdpType, sdp: self.sdp) + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Services/Message.swift b/iOS/Ringo/Ringo/WebRTC/Services/Message.swift new file mode 100644 index 0000000..93f49dd --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Services/Message.swift @@ -0,0 +1,49 @@ +// +// Message.swift +// WebRTC-Demo +// +// Created by Stasel on 20/02/2019. +// Copyright © 2019 Stasel. All rights reserved. +// + +import Foundation + +enum Message { + case sdp(SessionDescription) + case candidate(IceCandidate) +} + +extension Message: Codable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(String.self, forKey: .type) + switch type { + case String(describing: SessionDescription.self): + self = .sdp(try container.decode(SessionDescription.self, forKey: .payload)) + case String(describing: IceCandidate.self): + self = .candidate(try container.decode(IceCandidate.self, forKey: .payload)) + default: + throw DecodeError.unknownType + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .sdp(let sessionDescription): + try container.encode(sessionDescription, forKey: .payload) + try container.encode(String(describing: SessionDescription.self), forKey: .type) + case .candidate(let iceCandidate): + try container.encode(iceCandidate, forKey: .payload) + try container.encode(String(describing: IceCandidate.self), forKey: .type) + } + } + + enum DecodeError: Error { + case unknownType + } + + enum CodingKeys: String, CodingKey { + case type, payload + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift b/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift new file mode 100644 index 0000000..236c17d --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift @@ -0,0 +1,93 @@ +// +// SignalClient.swift +// WebRTC +// +// Created by Stasel on 20/05/2018. +// Copyright © 2018 Stasel. All rights reserved. +// + +import Foundation +import WebRTC + +protocol SignalClientDelegate: AnyObject { + func signalClientDidConnect(_ signalClient: SignalingClient) + func signalClientDidDisconnect(_ signalClient: SignalingClient) + func signalClient(_ signalClient: SignalingClient, didReceiveRemoteSdp sdp: RTCSessionDescription) + func signalClient(_ signalClient: SignalingClient, didReceiveCandidate candidate: RTCIceCandidate) +} + +final class SignalingClient { + + private let decoder = JSONDecoder() + private let encoder = JSONEncoder() + private let webSocket: WebSocketProvider + weak var delegate: SignalClientDelegate? + + init(webSocket: WebSocketProvider) { + self.webSocket = webSocket + } + + func connect() { + self.webSocket.delegate = self + self.webSocket.connect() + } + + func send(sdp rtcSdp: RTCSessionDescription) { + let message = Message.sdp(SessionDescription(from: rtcSdp)) + do { + let dataMessage = try self.encoder.encode(message) + + self.webSocket.send(data: dataMessage) + } + catch { + debugPrint("Warning: Could not encode sdp: \(error)") + } + } + + func send(candidate rtcIceCandidate: RTCIceCandidate) { + let message = Message.candidate(IceCandidate(from: rtcIceCandidate)) + do { + let dataMessage = try self.encoder.encode(message) + self.webSocket.send(data: dataMessage) + } + catch { + debugPrint("Warning: Could not encode candidate: \(error)") + } + } +} + + +extension SignalingClient: WebSocketProviderDelegate { + func webSocketDidConnect(_ webSocket: WebSocketProvider) { + self.delegate?.signalClientDidConnect(self) + } + + func webSocketDidDisconnect(_ webSocket: WebSocketProvider) { + self.delegate?.signalClientDidDisconnect(self) + + // try to reconnect every two seconds + DispatchQueue.global().asyncAfter(deadline: .now() + 2) { + debugPrint("Trying to reconnect to signaling server...") + self.webSocket.connect() + } + } + + func webSocket(_ webSocket: WebSocketProvider, didReceiveData data: Data) { + let message: Message + do { + message = try self.decoder.decode(Message.self, from: data) + } + catch { + debugPrint("Warning: Could not decode incoming message: \(error)") + return + } + + switch message { + case .candidate(let iceCandidate): + self.delegate?.signalClient(self, didReceiveCandidate: iceCandidate.rtcIceCandidate) + case .sdp(let sessionDescription): + self.delegate?.signalClient(self, didReceiveRemoteSdp: sessionDescription.rtcSessionDescription) + } + + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebRTCClient.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebRTCClient.swift new file mode 100644 index 0000000..17d9136 --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebRTCClient.swift @@ -0,0 +1,328 @@ +// +// WebRTCClient.swift +// WebRTC +// +// Created by Stasel on 20/05/2018. +// Copyright © 2018 Stasel. All rights reserved. +// + +import Foundation +import WebRTC + +protocol WebRTCClientDelegate: AnyObject { + func webRTCClient(_ client: WebRTCClient, didDiscoverLocalCandidate candidate: RTCIceCandidate) + func webRTCClient(_ client: WebRTCClient, didChangeConnectionState state: RTCIceConnectionState) + func webRTCClient(_ client: WebRTCClient, didReceiveData data: Data) +} + +final class WebRTCClient: NSObject { + + // The `RTCPeerConnectionFactory` is in charge of creating new RTCPeerConnection instances. + // A new RTCPeerConnection should be created every new call, but the factory is shared. + private static let factory: RTCPeerConnectionFactory = { + RTCInitializeSSL() + let videoEncoderFactory = RTCDefaultVideoEncoderFactory() + let videoDecoderFactory = RTCDefaultVideoDecoderFactory() + return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory) + }() + + weak var delegate: WebRTCClientDelegate? + private let peerConnection: RTCPeerConnection + private let rtcAudioSession = RTCAudioSession.sharedInstance() + private let audioQueue = DispatchQueue(label: "audio") + private let mediaConstrains = [kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueTrue, + kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueTrue] + private var videoCapturer: RTCVideoCapturer? + private var localVideoTrack: RTCVideoTrack? + private var remoteVideoTrack: RTCVideoTrack? + private var localDataChannel: RTCDataChannel? + private var remoteDataChannel: RTCDataChannel? + + @available(*, unavailable) + override init() { + fatalError("WebRTCClient:init is unavailable") + } + + required init(iceServers: [String]) { + let config = RTCConfiguration() + config.iceServers = [RTCIceServer(urlStrings: iceServers)] + + // Unified plan is more superior than planB + config.sdpSemantics = .unifiedPlan + + // gatherContinually will let WebRTC to listen to any network changes and send any new candidates to the other client + config.continualGatheringPolicy = .gatherContinually + + // Define media constraints. DtlsSrtpKeyAgreement is required to be true to be able to connect with web browsers. + let constraints = RTCMediaConstraints(mandatoryConstraints: nil, + optionalConstraints: ["DtlsSrtpKeyAgreement":kRTCMediaConstraintsValueTrue]) + + guard let peerConnection = WebRTCClient.factory.peerConnection(with: config, constraints: constraints, delegate: nil) else { + fatalError("Could not create new RTCPeerConnection") + } + + self.peerConnection = peerConnection + + super.init() + self.createMediaSenders() + self.configureAudioSession() + self.peerConnection.delegate = self + } + + // MARK: Signaling + func offer(completion: @escaping (_ sdp: RTCSessionDescription) -> Void) { + let constrains = RTCMediaConstraints(mandatoryConstraints: self.mediaConstrains, + optionalConstraints: nil) + self.peerConnection.offer(for: constrains) { (sdp, error) in + guard let sdp = sdp else { + return + } + + self.peerConnection.setLocalDescription(sdp, completionHandler: { (error) in + completion(sdp) + }) + } + } + + func answer(completion: @escaping (_ sdp: RTCSessionDescription) -> Void) { + let constrains = RTCMediaConstraints(mandatoryConstraints: self.mediaConstrains, + optionalConstraints: nil) + self.peerConnection.answer(for: constrains) { (sdp, error) in + guard let sdp = sdp else { + return + } + + self.peerConnection.setLocalDescription(sdp, completionHandler: { (error) in + completion(sdp) + }) + } + } + + func set(remoteSdp: RTCSessionDescription, completion: @escaping (Error?) -> ()) { + self.peerConnection.setRemoteDescription(remoteSdp, completionHandler: completion) + } + + func set(remoteCandidate: RTCIceCandidate, completion: @escaping (Error?) -> ()) { + self.peerConnection.add(remoteCandidate, completionHandler: completion) + } + + // MARK: Media + func startCaptureLocalVideo(renderer: RTCVideoRenderer) { + guard let capturer = self.videoCapturer as? RTCCameraVideoCapturer else { + return + } + + guard + let frontCamera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == .front }), + + // choose highest res + let format = (RTCCameraVideoCapturer.supportedFormats(for: frontCamera).sorted { (f1, f2) -> Bool in + let width1 = CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width + let width2 = CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width + return width1 < width2 + }).last, + + // choose highest fps + let fps = (format.videoSupportedFrameRateRanges.sorted { return $0.maxFrameRate < $1.maxFrameRate }.last) else { + return + } + + capturer.startCapture(with: frontCamera, + format: format, + fps: Int(fps.maxFrameRate)) + + self.localVideoTrack?.add(renderer) + } + + func renderRemoteVideo(to renderer: RTCVideoRenderer) { + self.remoteVideoTrack?.add(renderer) + } + + private func configureAudioSession() { + self.rtcAudioSession.lockForConfiguration() + do { + try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord) + try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat) + } catch let error { + debugPrint("Error changeing AVAudioSession category: \(error)") + } + self.rtcAudioSession.unlockForConfiguration() + } + + private func createMediaSenders() { + let streamId = "stream" + + // Audio + let audioTrack = self.createAudioTrack() + self.peerConnection.add(audioTrack, streamIds: [streamId]) + + // Video + let videoTrack = self.createVideoTrack() + self.localVideoTrack = videoTrack + self.peerConnection.add(videoTrack, streamIds: [streamId]) + self.remoteVideoTrack = self.peerConnection.transceivers.first { $0.mediaType == .video }?.receiver.track as? RTCVideoTrack + + // Data + if let dataChannel = createDataChannel() { + dataChannel.delegate = self + self.localDataChannel = dataChannel + } + } + + private func createAudioTrack() -> RTCAudioTrack { + let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) + let audioSource = WebRTCClient.factory.audioSource(with: audioConstrains) + let audioTrack = WebRTCClient.factory.audioTrack(with: audioSource, trackId: "audio0") + return audioTrack + } + + private func createVideoTrack() -> RTCVideoTrack { + let videoSource = WebRTCClient.factory.videoSource() + + #if targetEnvironment(simulator) + self.videoCapturer = RTCFileVideoCapturer(delegate: videoSource) + #else + self.videoCapturer = RTCCameraVideoCapturer(delegate: videoSource) + #endif + + let videoTrack = WebRTCClient.factory.videoTrack(with: videoSource, trackId: "video0") + return videoTrack + } + + // MARK: Data Channels + private func createDataChannel() -> RTCDataChannel? { + let config = RTCDataChannelConfiguration() + guard let dataChannel = self.peerConnection.dataChannel(forLabel: "WebRTCData", configuration: config) else { + debugPrint("Warning: Couldn't create data channel.") + return nil + } + return dataChannel + } + + func sendData(_ data: Data) { + let buffer = RTCDataBuffer(data: data, isBinary: true) + self.remoteDataChannel?.sendData(buffer) + } +} + +extension WebRTCClient: RTCPeerConnectionDelegate { + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { + debugPrint("peerConnection new signaling state: \(stateChanged)") + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { + debugPrint("peerConnection did add stream") + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { + debugPrint("peerConnection did remove stream") + } + + func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { + debugPrint("peerConnection should negotiate") + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { + debugPrint("peerConnection new connection state: \(newState)") + self.delegate?.webRTCClient(self, didChangeConnectionState: newState) + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { + debugPrint("peerConnection new gathering state: \(newState)") + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { + self.delegate?.webRTCClient(self, didDiscoverLocalCandidate: candidate) + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { + debugPrint("peerConnection did remove candidate(s)") + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { + debugPrint("peerConnection did open data channel") + self.remoteDataChannel = dataChannel + } +} +extension WebRTCClient { + private func setTrackEnabled(_ type: T.Type, isEnabled: Bool) { + peerConnection.transceivers + .compactMap { return $0.sender.track as? T } + .forEach { $0.isEnabled = isEnabled } + } +} + +// MARK: - Video control +extension WebRTCClient { + func hideVideo() { + self.setVideoEnabled(false) + } + func showVideo() { + self.setVideoEnabled(true) + } + private func setVideoEnabled(_ isEnabled: Bool) { + setTrackEnabled(RTCVideoTrack.self, isEnabled: isEnabled) + } +} +// MARK:- Audio control +extension WebRTCClient { + func muteAudio() { + self.setAudioEnabled(false) + } + + func unmuteAudio() { + self.setAudioEnabled(true) + } + + // Fallback to the default playing device: headphones/bluetooth/ear speaker + func speakerOff() { + self.audioQueue.async { [weak self] in + guard let self = self else { + return + } + + self.rtcAudioSession.lockForConfiguration() + do { + try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord) + try self.rtcAudioSession.overrideOutputAudioPort(.none) + } catch let error { + debugPrint("Error setting AVAudioSession category: \(error)") + } + self.rtcAudioSession.unlockForConfiguration() + } + } + + // Force speaker + func speakerOn() { + self.audioQueue.async { [weak self] in + guard let self = self else { + return + } + + self.rtcAudioSession.lockForConfiguration() + do { + try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord) + try self.rtcAudioSession.overrideOutputAudioPort(.speaker) + try self.rtcAudioSession.setActive(true) + } catch let error { + debugPrint("Couldn't force audio to speaker: \(error)") + } + self.rtcAudioSession.unlockForConfiguration() + } + } + + private func setAudioEnabled(_ isEnabled: Bool) { + setTrackEnabled(RTCAudioTrack.self, isEnabled: isEnabled) + } +} + +extension WebRTCClient: RTCDataChannelDelegate { + func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { + debugPrint("dataChannel did change state: \(dataChannel.readyState)") + } + + func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { + self.delegate?.webRTCClient(self, didReceiveData: buffer.data) + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift new file mode 100644 index 0000000..e38f3a5 --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift @@ -0,0 +1,70 @@ +// +// NativeSocketProvider.swift +// WebRTC-Demo +// +// Created by stasel on 15/07/2019. +// Copyright © 2019 stasel. All rights reserved. +// + +import Foundation + +@available(iOS 13.0, *) +class NativeWebSocket: NSObject, WebSocketProvider { + + var delegate: WebSocketProviderDelegate? + private let url: URL + private var socket: URLSessionWebSocketTask? + private lazy var urlSession: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + + init(url: URL) { + self.url = url + super.init() + } + + func connect() { + let socket = urlSession.webSocketTask(with: url) + socket.resume() + self.socket = socket + self.readMessage() + } + + func send(data: Data) { + self.socket?.send(.data(data)) { _ in } + } + + private func readMessage() { + self.socket?.receive { [weak self] message in + guard let self = self else { return } + + switch message { + case .success(.data(let data)): + self.delegate?.webSocket(self, didReceiveData: data) + self.readMessage() + + case .success: + debugPrint("Warning: Expected to receive data format but received a string. Check the websocket server config.") + self.readMessage() + + case .failure: + self.disconnect() + } + } + } + + private func disconnect() { + self.socket?.cancel() + self.socket = nil + self.delegate?.webSocketDidDisconnect(self) + } +} + +@available(iOS 13.0, *) +extension NativeWebSocket: URLSessionWebSocketDelegate, URLSessionDelegate { + func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { + self.delegate?.webSocketDidConnect(self) + } + + func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { + self.disconnect() + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift new file mode 100644 index 0000000..9810add --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift @@ -0,0 +1,51 @@ +// +// StarscreamProvider.swift +// WebRTC-Demo +// +// Created by stasel on 15/07/2019. +// Copyright © 2019 stasel. All rights reserved. +// + +import Foundation +import Starscream + +class StarscreamWebSocket: WebSocketProvider { + + var delegate: WebSocketProviderDelegate? + private let socket: WebSocket + + init(url: URL) { + self.socket = WebSocket(request: URLRequest(url: url)) + self.socket.delegate = self + } + + func connect() { + self.socket.connect() + } + + func send(data: Data) { + self.socket.write(data: data) + } +} + +extension StarscreamWebSocket: Starscream.WebSocketDelegate { + func didReceive(event: Starscream.WebSocketEvent, client: Starscream.WebSocketClient) { + print("idk") + } + + + func didReceive(event: Starscream.WebSocketEvent, client: Starscream.WebSocket) { + switch event { + case .connected: + self.delegate?.webSocketDidConnect(self) + case .disconnected: + self.delegate?.webSocketDidDisconnect(self) + case .text: + debugPrint("Warning: Expected to receive data format but received a string. Check the websocket server config.") + case .binary(let data): + self.delegate?.webSocket(self, didReceiveData: data) + default: + break + } + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift new file mode 100644 index 0000000..075ae66 --- /dev/null +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift @@ -0,0 +1,21 @@ +// +// File.swift +// WebRTC-Demo +// +// Created by stasel on 15/07/2019. +// Copyright © 2019 stasel. All rights reserved. +// + +import Foundation + +protocol WebSocketProvider: AnyObject { + var delegate: WebSocketProviderDelegate? { get set } + func connect() + func send(data: Data) +} + +protocol WebSocketProviderDelegate: AnyObject { + func webSocketDidConnect(_ webSocket: WebSocketProvider) + func webSocketDidDisconnect(_ webSocket: WebSocketProvider) + func webSocket(_ webSocket: WebSocketProvider, didReceiveData data: Data) +}