Skip to content

Instantly share code, notes, and snippets.

@saroar
Last active October 30, 2018 03:50
Show Gist options
  • Save saroar/20cde456d4952f0f3644c187303bdd7b to your computer and use it in GitHub Desktop.
Save saroar/20cde456d4952f0f3644c187303bdd7b to your computer and use it in GitHub Desktop.
import UIKit
class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
private var didReceiveRemoteStream:( () -> Void )?
private var onCreatedLocalSdp:((_ localSdp:NSDictionary) -> Void )?
private let factory = RTCPeerConnectionFactory()
private var localStream: RTCMediaStream?
private var localRenderView = RTCEAGLVideoView()
private let _localView = UIView(frame:CGRect(x:0, y:0, width: windowWidth(), height: windowHeight()))
private var remoteStream: RTCMediaStream?
private var remoteRenderView = RTCEAGLVideoView()
private let _remoteView = UIView(frame: CGRect(x: 0, y: 0, width: windowWidth(), height: windowWidth()))
private var peerConnections = [String: RTCPeerConnection]()
private var peerConnectionUserList = [String]()
static let sharedInstance = WebRTC()
private override init() {
super.init()
}
deinit {
for peerConnection in peerConnections {
if let stream = peerConnection.value.localStreams.first {
peerConnection.value.remove(stream)
}
}
}
// MARK: inerface
func localView() -> UIView {
return _localView
}
func remoteView() -> UIView {
return _remoteView
}
func setup() {
setupLocalStream()
}
func connect(userID: String, iceServerUrlList: [String], onCreatedLocalSdp: @escaping ((_ localSdp:NSDictionary) -> Void), didReceiveRemoteStream: @escaping ( () -> Void ) ) {
self.onCreatedLocalSdp = onCreatedLocalSdp
self.didReceiveRemoteStream = didReceiveRemoteStream
let configuration = RTCConfiguration()
configuration.iceServers = [RTCIceServer(urlStrings: iceServerUrlList)]
peerConnectionUserList.append(userID)
peerConnections[userID] = factory.peerConnection(with: configuration, constraints: WebRTCUtil.peerConnectionConstraints(), delegate: self)
peerConnections[userID]?.add(localStream!)
}
func diconnect(userID: String, iceServerUrlList: [String], onCreatedLocalSdp: @escaping ((_ localSdp:NSDictionary) -> Void), didReceiveRemoteStream: @escaping ( () -> Void ) ) {
}
func flipCamera() {
if let mysource = localStream?.videoTracks.first?.source as? RTCAVFoundationVideoSource {
mysource.useBackCamera = !mysource.useBackCamera
}
}
func enableVideo() {
if let mysvideo = localStream?.videoTracks.first {
mysvideo.isEnabled = !mysvideo.isEnabled
}
}
func enableAudio() {
if let myAudio = localStream?.audioTracks.first {
myAudio.isEnabled = !myAudio.isEnabled
}
}
// Answer
func receiveAnswer(hostID: String, remoteSdp: NSDictionary) {
_receiveAnswer(hostID: hostID, remoteSdp: remoteSdp)
}
// Offer
func receiveOffer(joinUID: String, remoteSdp: NSDictionary) {
_receiveOffer(joinUID: joinUID, remoteSdp: remoteSdp)
}
// Offer
func createOffer(hostID: String) {
_createOffer(hostID: hostID)
}
// MARK: implements
private func _receiveAnswer(hostID: String, remoteSdp: NSDictionary) {
guard let sdpContents = remoteSdp.object(forKey: "sdp") as? String else {
print("noSDp")
return
}
let sdp = RTCSessionDescription(type: .answer, sdp: sdpContents)
// 1. remote SDP
peerConnections[hostID]?.setRemoteDescription(sdp, completionHandler: { (error) in print(105, error?.localizedDescription) })
}
private func _receiveOffer(joinUID: String, remoteSdp: NSDictionary) {
guard let sdpContents = remoteSdp.object(forKey: "sdp") as? String else {
print("noSDp")
return
}
// 1. remote SDP
let remoteSdp = RTCSessionDescription(type: .offer, sdp: sdpContents)
peerConnections[joinUID]?.setRemoteDescription(remoteSdp, completionHandler: { (error) in
// if any user offer me i commend it for one user
// 2. answer
self.peerConnections[joinUID]?.answer(for: WebRTCUtil.answerConstraints(), completionHandler: { (sdp, error) in
guard let sdp = sdp else {
print("can not create sdp")
return
}
print(128, error?.localizedDescription)
// 3.SDP
self.peerConnections[joinUID]?.setLocalDescription(sdp, completionHandler: { (error) in print(131, error?.localizedDescription) })
// 4. peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState)
// complete Answer
})
})
}
private func _createOffer(hostID: String) {
// 1. offer
peerConnections[hostID]?.offer(for: WebRTCUtil.mediaStreamConstraints(), completionHandler: { (description, error) in
guard let description = description else {
print("----- no description ----")
return
}
print(149, error?.localizedDescription)
// 2.SDP
self.peerConnections[hostID]?.setLocalDescription(description, completionHandler: { (error) in
print(153, error?.localizedDescription)
})
// 3. peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState)
// complete offer
})
}
private func setupLocalStream() {
let streamId = WebRTCUtil.idWithPrefix(prefix: "stream")
localStream = factory.mediaStream(withStreamId: streamId)
setupView()
setupLocalVideoTrack()
setupLocalAudioTrack()
}
private func setupView() {
localRenderView.delegate = self
_localView.backgroundColor = UIColor.white
_localView.frame.origin = CGPoint(x: 20, y: _remoteView.frame.size.height - (_localView.frame.size.height / 2))
_localView.addSubview(localRenderView)
remoteRenderView.delegate = self
_remoteView.backgroundColor = UIColor.lightGray
_remoteView.addSubview(remoteRenderView)
}
private func setupLocalVideoTrack() {
let localVideoSource = factory.avFoundationVideoSource(with: WebRTCUtil.mediaStreamConstraints())
let localVideoTrack = factory.videoTrack(with: localVideoSource, trackId: WebRTCUtil.idWithPrefix(prefix: "video"))
if let avSource = localVideoTrack.source as? RTCAVFoundationVideoSource {
avSource.useBackCamera = false
}
localVideoTrack.add(localRenderView)
localStream?.addVideoTrack(localVideoTrack)
}
private func setupLocalAudioTrack() {
let localAudioTrack = factory.audioTrack(withTrackId: WebRTCUtil.idWithPrefix(prefix: "audio"))
localStream?.addAudioTrack(localAudioTrack)
}
// MARK: RTCPeerConnectionDelegate
// いったんスルー
public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {}
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {}
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { }
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {}
public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {}
public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
print("peerConnection didAdd stream:")
if stream == localStream {
return
}
self.remoteStream = stream
if let remoteVideoTrack = stream.videoTracks.first {
DispatchQueue.main.async {
remoteVideoTrack.add(self.remoteRenderView)
}
}
if let callback = self.didReceiveRemoteStream {
DispatchQueue.main.async {
callback()
}
self.didReceiveRemoteStream = nil
}
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
print("peerConnection didChange newState: RTCIceGatheringState, \(newState)")
if newState != .complete {
return
}
print("1. userList", peerConnectionUserList)
for user in peerConnectionUserList {
print("2. user, self.peerConnection[user]", user, self.peerConnections[user])
if self.peerConnections[user] == peerConnection {
print("peerConnection connected", peerConnection)
guard let callback = self.onCreatedLocalSdp, let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnections[user]?.localDescription) else {
print("no localDescription")
return
}
callback(localDescription)
self.onCreatedLocalSdp = nil
print("3. peerConnection didChange")
}
}
}
// MARK: RTCEAGLVideoViewDelegate
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
print("---- didChangeVideoSize -----")
let ratio:CGFloat = size.width / size.height
if videoView == localRenderView {
let parentWidth = _localView.frame.size.width
_ = parentWidth * ratio
// localRenderView.frame = CGRect(x: (parentWidth - width) / 2, y: 2, width: width, height: _localView.frame.size.height-4)
localRenderView.frame = CGRect(x: 0, y: 0, width: size.width, height: _localView.frame.size.height)
} else if videoView == remoteRenderView {
let parentWidth = _remoteView.frame.size.width
_ = parentWidth * ratio
// remoteRenderView.frame = CGRect(x: (parentWidth - width) / 2, y: 0, width: width, height: _remoteView.frame.size.height)
remoteRenderView.frame = CGRect(x: 0, y: 0, width: size.width, height: _remoteView.frame.size.height)
}
}
}
@highchops1981
Copy link

I think that multiple "remoteRenderViews" are required for each joinUID.

@highchops1981
Copy link

you need "func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate)" .
Send RTCIceCandidate to each joinUID and Add it to PeerConnection of each joinUID.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment