chore: finished webrtc swift implementation, ready to test
parent
ec5f5a1d1a
commit
056eed8c70
|
@ -11,16 +11,19 @@ import WebRTC
|
|||
|
||||
public enum RTCWrapperState {
|
||||
case disconnected
|
||||
case connecting
|
||||
case ready
|
||||
case connected
|
||||
}
|
||||
|
||||
class PeerConnectionWrapper: NSObject{
|
||||
// State
|
||||
var state: RTCWrapperState = .disconnected
|
||||
var remoteUserId: String?
|
||||
var remoteDeviceId: String?
|
||||
|
||||
// WebRTC initialization
|
||||
var connectionFactory: RTCPeerConnectionFactory?
|
||||
var signalingApiProvider: SignalingApiProvider?
|
||||
var peerConnection: RTCPeerConnection?
|
||||
var remoteIceCandidates: [RTCIceCandidate] = []
|
||||
|
||||
|
@ -38,15 +41,19 @@ class PeerConnectionWrapper: NSObject{
|
|||
super.init()
|
||||
}
|
||||
|
||||
public convenience init(connectionFactory: RTCPeerConnectionFactory) {
|
||||
public convenience init(connectionFactory: RTCPeerConnectionFactory, signalingApiProvider: SignalingApiProvider, remoteUserId: String, remoteDeviceId: String) {
|
||||
self.init()
|
||||
self.connectionFactory = connectionFactory
|
||||
self.signalingApiProvider = signalingApiProvider
|
||||
self.remoteUserId = remoteUserId
|
||||
self.remoteDeviceId = remoteDeviceId
|
||||
|
||||
initialisePeerConnection()
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
if let peerConnection = self.peerConnection {
|
||||
self.state = .connecting
|
||||
self.state = .connected
|
||||
let localStream = self.localStream()
|
||||
peerConnection.add(localStream)
|
||||
}
|
||||
|
@ -58,10 +65,11 @@ class PeerConnectionWrapper: NSObject{
|
|||
if let stream = peerConnection.localStreams.first {
|
||||
peerConnection.remove(stream)
|
||||
}
|
||||
self.state = .disconnected
|
||||
}
|
||||
}
|
||||
|
||||
public func createOffer() {
|
||||
public func createOffer(userId: String, deviceId: String) {
|
||||
if let peerConnection = self.peerConnection {
|
||||
|
||||
peerConnection.offer(for: self.channelConstraint, completionHandler: { [weak self] (sdp, error) in
|
||||
|
@ -69,15 +77,16 @@ class PeerConnectionWrapper: NSObject{
|
|||
guard self != nil else { return }
|
||||
|
||||
if let error = error {
|
||||
// Throw an error or smth
|
||||
print(error)
|
||||
} else {
|
||||
// Use the sdp generated
|
||||
self?.signalingApiProvider?.postDataToUser(userId: userId, deviceId: deviceId, data: sdp!.sdp, event: "offer")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func handleAnswer(withRemoteSDP remoteSdp: String) {
|
||||
public func handleAnswer(remoteSdp: String) {
|
||||
if let peerConnection = self.peerConnection {
|
||||
let sessionDescription = RTCSessionDescription.init(type: .answer, sdp: remoteSdp)
|
||||
|
||||
|
@ -87,15 +96,16 @@ class PeerConnectionWrapper: NSObject{
|
|||
|
||||
if let error = error {
|
||||
// Throw an error
|
||||
print(error)
|
||||
} else {
|
||||
// handle the remote sdp
|
||||
this.state = .connected
|
||||
this.state = .ready
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func createAnswerForOffer(withRemoteSDP remoteSdp: String) {
|
||||
public func createAnswerForOffer(userId: String, deviceId: String, remoteSdp: String) {
|
||||
if let peerConnection = self.peerConnection {
|
||||
let sessionDescription = RTCSessionDescription.init(type: .answer, sdp: remoteSdp)
|
||||
peerConnection.setRemoteDescription(sessionDescription, completionHandler: { [weak self] (error) in
|
||||
|
@ -104,6 +114,7 @@ class PeerConnectionWrapper: NSObject{
|
|||
|
||||
if let error = error {
|
||||
// Throw an error
|
||||
print(error)
|
||||
} else {
|
||||
// handle the remote sdp
|
||||
|
||||
|
@ -112,9 +123,11 @@ class PeerConnectionWrapper: NSObject{
|
|||
{ (sdp, error) in
|
||||
if let error = error {
|
||||
// Throw an error
|
||||
print(error)
|
||||
} else {
|
||||
// handle generated local sdp
|
||||
this.state = .connected
|
||||
self?.signalingApiProvider?.postDataToUser(userId: userId, deviceId: deviceId, data: sdp!.sdp, event: "answer")
|
||||
this.state = .ready
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -182,7 +195,8 @@ extension PeerConnectionWrapper: RTCPeerConnectionDelegate {
|
|||
}
|
||||
|
||||
func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
|
||||
<#code#>
|
||||
let candidateString = "\(candidate.sdp)-\(candidate.sdpMLineIndex)-\(candidate.sdpMid ?? "")"
|
||||
self.signalingApiProvider?.postDataToUser(userId: remoteUserId!, deviceId: remoteDeviceId!, data: candidateString, event: "ice-candidate")
|
||||
}
|
||||
|
||||
func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
||||
|
|
|
@ -12,26 +12,58 @@ import WebRTC
|
|||
class PeerManager: NSObject {
|
||||
// WebRTC initialization
|
||||
var connectionFactory: RTCPeerConnectionFactory?
|
||||
var signalingApiProvider: SignalingApiProvider?
|
||||
var eventSource: EventSource = EventSource(url: "https://staging.beepvoice.app/signal")
|
||||
|
||||
// List of users
|
||||
var peerList: [String: PeerConnectionWrapper] = [:]
|
||||
var whitePeerList: [String] = []
|
||||
var activeConversation: String?
|
||||
|
||||
public override init() {
|
||||
super.init()
|
||||
initialisePeerConnectionFactory()
|
||||
}
|
||||
|
||||
public func join(conversationId: String, usersIds: [String]) {
|
||||
public func join(conversationId: String) {
|
||||
let userOpList = signalingApiProvider?.getConversationUsers(conversationId: conversationId)
|
||||
activeConversation = conversationId
|
||||
|
||||
guard let userList = userOpList else {
|
||||
// Error incorrect conversation ID
|
||||
return
|
||||
}
|
||||
|
||||
for user in userList {
|
||||
whitePeerList.append(user)
|
||||
|
||||
let deviceOpList = signalingApiProvider?.getUserDevices(userId: user)
|
||||
|
||||
guard let deviceList = deviceOpList else {
|
||||
// Error incorrect user ID
|
||||
return
|
||||
}
|
||||
|
||||
for device in deviceList {
|
||||
let connection: PeerConnectionWrapper = PeerConnectionWrapper(connectionFactory: self.connectionFactory!, signalingApiProvider: self.signalingApiProvider!, remoteUserId: user, remoteDeviceId: device)
|
||||
self.peerList["\(user)-\(device)"] = connection
|
||||
connection.createOffer(userId: user, deviceId: device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func exit() {
|
||||
for (_, connection) in peerList {
|
||||
connection.disconnect()
|
||||
}
|
||||
|
||||
whitePeerList = []
|
||||
peerList = [:]
|
||||
activeConversation = nil
|
||||
}
|
||||
|
||||
public func get() -> String {
|
||||
|
||||
public func get() -> String? {
|
||||
return activeConversation
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,15 +75,73 @@ private extension PeerManager {
|
|||
|
||||
func initialiseEventSource() {
|
||||
eventSource.addEventListener("offer") { (id, event, data) in
|
||||
|
||||
guard let id = id, let data = data else {
|
||||
// Incorrect packet type error
|
||||
}
|
||||
|
||||
// Handling offers, if in list accept
|
||||
if self.whitePeerList.contains(id) {
|
||||
// Split id into user and device
|
||||
let idArr = id.components(separatedBy: "-")
|
||||
|
||||
// Check id format
|
||||
if idArr.count != 2 {
|
||||
// Incorrect id format error
|
||||
return
|
||||
}
|
||||
|
||||
let connection: PeerConnectionWrapper = PeerConnectionWrapper(connectionFactory: self.connectionFactory!, signalingApiProvider: self.signalingApiProvider!, remoteUserId: idArr[0], remoteDeviceId: idArr[1])
|
||||
self.peerList[id] = connection
|
||||
|
||||
|
||||
connection.createAnswerForOffer(userId: idArr[0], deviceId: idArr[1], remoteSdp: data)
|
||||
connection.connect()
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.addEventListener("answer") { (id, event, data) in
|
||||
|
||||
guard let id = id, let data = data else {
|
||||
// Incorrect packet type error
|
||||
}
|
||||
|
||||
// Handling answers, if in list accept
|
||||
if self.whitePeerList.contains(id) {
|
||||
let connection: PeerConnectionWrapper = self.peerList[id]!
|
||||
connection.handleAnswer(remoteSdp: data)
|
||||
connection.connect()
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.addEventListener("ice-candidate") { (id, event, data) in
|
||||
|
||||
guard let id = id, let data = data else {
|
||||
// Incorrect packet type error
|
||||
return
|
||||
}
|
||||
|
||||
// Handling ice candidates, if in list accept
|
||||
if self.whitePeerList.contains(id) {
|
||||
let connection: PeerConnectionWrapper = self.peerList[id]!
|
||||
|
||||
let dataArr = data.components(separatedBy: "-")
|
||||
|
||||
// Check dataArr size of 3
|
||||
if dataArr.count != 3 {
|
||||
// Incorrect data format error
|
||||
return
|
||||
}
|
||||
|
||||
// Convert sdpMLineIndex to Int32
|
||||
guard let sdpMLineIndex = Int32(dataArr[1]) else {
|
||||
// Invalid sdpMLineIndex error
|
||||
return
|
||||
}
|
||||
|
||||
let iceCandidate = RTCIceCandidate(sdp: dataArr[0], sdpMLineIndex: sdpMLineIndex, sdpMid: dataArr[1])
|
||||
connection.addIceCandidate(iceCandidate: iceCandidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebRTC
|
||||
|
||||
class SignalingApiProvider: NSObject {
|
||||
var authToken: String
|
||||
|
@ -20,8 +21,9 @@ class SignalingApiProvider: NSObject {
|
|||
self.authToken = authToken
|
||||
}
|
||||
|
||||
public func getUserDevices(userId: String) -> [String] {
|
||||
public func getUserDevices(userId: String) -> [String]? {
|
||||
let url: URL = URL(string: "http://staging.beepvoice.app/user/\(userId)/devices")!
|
||||
var deviceList: [String] = []
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
|
||||
request.httpMethod = "GET"
|
||||
|
@ -33,9 +35,17 @@ class SignalingApiProvider: NSObject {
|
|||
|
||||
print(response)
|
||||
do {
|
||||
if let jsonResult = try JSONSerialization.jsonObject(with: dataVal, options: []) as? NSDictionary {
|
||||
if let jsonResult = try JSONSerialization.jsonObject(with: dataVal, options: []) as? [String] {
|
||||
print("Synchronous\(jsonResult)")
|
||||
// Need code to convert this to an array of strings
|
||||
// convert this to an array of strings
|
||||
for device in jsonResult {
|
||||
deviceList.append(device)
|
||||
}
|
||||
|
||||
return deviceList
|
||||
} else {
|
||||
// Invalid response format
|
||||
return nil
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print(error.localizedDescription)
|
||||
|
@ -43,10 +53,13 @@ class SignalingApiProvider: NSObject {
|
|||
} catch let error as NSError {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getConversationUsers(conversationId: String) -> [String] {
|
||||
public func getConversationUsers(conversationId: String) -> [String]? {
|
||||
let url: URL = URL(string: "http://staging.beepvoice.app/user/conversation/\(conversationId)/member")!
|
||||
var userList: [String] = []
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
|
||||
request.httpMethod = "GET"
|
||||
|
@ -58,9 +71,21 @@ class SignalingApiProvider: NSObject {
|
|||
|
||||
print(response)
|
||||
do {
|
||||
if let jsonResult = try JSONSerialization.jsonObject(with: dataVal, options: []) as? NSDictionary {
|
||||
if let jsonResult = try JSONSerialization.jsonObject(with: dataVal, options: []) as? [Any] {
|
||||
print("Synchronous\(jsonResult)")
|
||||
// Need code to convert this to an array of strings
|
||||
for user in jsonResult {
|
||||
if let userObject = user as? [String: String] {
|
||||
guard let userId = userObject["id"] else {
|
||||
// Invalid response format
|
||||
return nil
|
||||
}
|
||||
|
||||
userList.append(userId)
|
||||
}
|
||||
}
|
||||
|
||||
return userList
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print(error.localizedDescription)
|
||||
|
@ -68,28 +93,29 @@ class SignalingApiProvider: NSObject {
|
|||
} catch let error as NSError {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public func postDataToUser(userId: String, deviceId: String) {
|
||||
// CHECK FOR WHEN DEVICE IS UNAVAILABLE
|
||||
public func postDataToUser(userId: String, deviceId: String, data: String, event: String) {
|
||||
let url: URL = URL(string: "http://staging.beepvoice.app/user/\(userId)/device/\(deviceId)")!
|
||||
|
||||
// prepare json data
|
||||
let json: [String: Any] = ["event": event, "data": data]
|
||||
|
||||
let jsonData = try? JSONSerialization.data(withJSONObject: json)
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = jsonData
|
||||
|
||||
let response: AutoreleasingUnsafeMutablePointer<URLResponse?>
|
||||
|
||||
do {
|
||||
let dataVal = try NSURLConnection.sendSynchronousRequest(request, returning: response)
|
||||
|
||||
let _ = try NSURLConnection.sendSynchronousRequest(request, returning: response)
|
||||
print(response)
|
||||
do {
|
||||
if let jsonResult = try JSONSerialization.jsonObject(with: dataVal, options: []) as? NSDictionary {
|
||||
print("Synchronous\(jsonResult)")
|
||||
// Need code to convert this to an array of strings
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue