LoginSignup
0
2

More than 5 years have passed since last update.

[webRTC for ios vol4] 複数台接続

Last updated at Posted at 2017-08-08

iOSで複数台接続やります。

成果物

流れ

流れはweb版と同じ。
roomKeyはとりあえず固定で。

参考

実装

こちらもweb版とほぼ同じ。

Wamp

簡易wampサーバーはこちら → webRTC for ios vol2

connectしてsubscribeする。
あとはsessionの提供。

Wamp.swift
import UIKit
import Swamp


enum WampTopic:String{

    case callme = "com.nakadoribook.webrtc.[roomId].callme"
    , close = "com.nakadoribook.webrtc.[roomId].close"
    , answer = "com.nakadoribook.webrtc.[roomId].[id].answer"
    , offer = "com.nakadoribook.webrtc.[roomId].[id].offer"
    , candidate = "com.nakadoribook.webrtc.[roomId].[id].candidate"

}

typealias WampOnOpenHandler = (()->())
typealias WampReceiveAnswerHandler = ((_ targetId:String, _ sdp:NSDictionary)->())
typealias WampReceiveOfferHandler = ((_ targetId:String, _ sdp:NSDictionary)->())
typealias WampReceiveCandidateHandler = ((_ targetId:String, _ candidate:NSDictionary)->())
typealias WampReceiveCallmeHandler = ((_ targetId:String)->())
typealias WampOncloseConnectionHandler = ((_ targetId:String)->())

typealias WampCallbacks = (onOpen:WampOnOpenHandler
    , onReceiveAnswer:WampReceiveAnswerHandler
    , onReceiveOffer:WampReceiveOfferHandler
    , onReceiveCallme:WampReceiveCallmeHandler
    , onCloseConnection:WampOncloseConnectionHandler)

class Wamp: NSObject, SwampSessionDelegate {

    static let sharedInstance = Wamp()
    private var _session:SwampSession!

    private override init() {
        super.init()        
    }

    var session:SwampSession{
        get{
            return _session
        }
    }

    func connect(){
        let swampTransport = WebSocketSwampTransport(wsEndpoint:  URL(string: "wss://nakadoribooks-webrtc.herokuapp.com")!)
        let swampSession = SwampSession(realm: "realm1", transport: swampTransport)
        swampSession.delegate = self
        swampSession.connect()
    }

    private func resultToSdp(results:[Any]?)->NSDictionary?{

        if let sdp = results?.first as? NSDictionary{
            return sdp;
        }

        return nil;
    }

    // MARK: SwampSessionDelegate

    func swampSessionHandleChallenge(_ authMethod: String, extra: [String: Any]) -> String{
        return SwampCraAuthHelper.sign("secret123", challenge: extra["challenge"] as! String)
    }

    func swampSessionConnected(_ session: SwampSession, sessionId: Int){
        self._session = session

        // subscribe
        session.subscribe(endpointAnswer(targetId: userId), onSuccess: { (subscription) in
        }, onError: { (details, error) in
        }) { (details, args, kwArgs) in
            guard let args = args, let targetId = args[0] as? String, let sdpString = args[1] as? String else{
                print("no args answer")
                return
            }

            let sdp = try! JSONSerialization.jsonObject(with: sdpString.data(using: .utf8)!, options: .allowFragments) as! NSDictionary
            self.callbacks.onReceiveAnswer(targetId, sdp)
        }

        session.subscribe(endpointOffer(targetId: userId), onSuccess: { (subscription) in
        }, onError: { (details, error) in
        }) { (details, args, kwArgs) in
            guard let args = args, let targetId = args[0] as? String, let sdpString = args[1] as? String else{
                print("no args offer")
                return
            }

            let sdp = try! JSONSerialization.jsonObject(with: sdpString.data(using: .utf8)!, options: .allowFragments) as! NSDictionary
            self.callbacks.onReceiveOffer(targetId, sdp)
        }

        session.subscribe(endpointCallme(), onSuccess: { (subscription) in
        }, onError: { (details, error) in
        }) { (details, args, kwArgs) in
            guard let args = args, let targetId = args[0] as? String else{
                print("no args callMe")
                return
            }

            if targetId == self.userId{
                return;
            }
            self.callbacks.onReceiveCallme(targetId)
        }

        session.subscribe(endpointClose(), onSuccess: { (subscription) in
        }, onError: { (details, error) in
        }) { (details, args, kwArgs) in
            guard let args = args, let targetId = args[0] as? String else{
                print("no args close")
                return
            }

            if targetId == self.userId{
                return;
            }

            self.callbacks.onCloseConnection(targetId)
        }

        self.callbacks.onOpen()
    }

    func swampSessionEnded(_ reason: String){
        print("swampSessionEnded: \(reason)")
    }

    private var roomKey:String!
    private var userId:String!
    private var callbacks:WampCallbacks!

    func setup(roomKey:String, userId:String, callbacks:WampCallbacks){
        self.roomKey = roomKey
        self.userId = userId
        self.callbacks = callbacks
    }

    static let HandshakeEndpint = "wss://nakadoribooks-webrtc.herokuapp.com"

    private func roomTopic(base:String)->String{
        return base.replacingOccurrences(of: "[roomId]", with: self.roomKey)
    }

    func endpointAnswer(targetId:String)->String{
        return self.roomTopic(base: WampTopic.answer.rawValue.replacingOccurrences(of: "[id]", with: targetId))
    }

    func endpointOffer(targetId:String)->String{
        return self.roomTopic(base: WampTopic.offer.rawValue.replacingOccurrences(of: "[id]", with: targetId))
    }

    func endpointCandidate(targetId:String)->String{
        return self.roomTopic(base: WampTopic.candidate.rawValue.replacingOccurrences(of: "[id]", with: targetId))
    }

    func endpointCallme()->String{
        return self.roomTopic(base: WampTopic.callme.rawValue)
    }

    func endpointClose()->String{
        return self.roomTopic(base: WampTopic.close.rawValue)
    }

}

RTCPeerConnectionまわり

static でlocalStreamを管理
接続の数だけインスタンスを作って相手のStreamを管理

WebRTC.swift
import UIKit

typealias WebRTCOnIceCandidateHandler = (_ candidate:NSDictionary) -> ()
typealias WebRTCOnAddedStream = (_ stream:RTCMediaStream, _ view:UIView) -> ()
typealias WebRTCOnRemoveStream = (_ stream:RTCMediaStream) -> ()
typealias WebRTCCallbacks = (onIceCandidate:WebRTCOnIceCandidateHandler, onAddedStream:WebRTCOnAddedStream, onRemoveStream:WebRTCOnRemoveStream)

class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {

    // ▼ inner class -----
    class LocalViewDelegate:NSObject, RTCEAGLVideoViewDelegate{

        private let localRenderView:RTCEAGLVideoView

        init(localRenderView:RTCEAGLVideoView){
            self.localRenderView = localRenderView
            super.init()

            localRenderView.delegate = self
        }

        // MARK: RTCEAGLVideoViewDelegate

        func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
            print("---- didChangeVideoSize -----")

            let ratio:CGFloat = size.width / size.height

            if ratio > 1.0{
                // 横長
                let height:CGFloat = WebRTC.ViewSize
                let width:CGFloat = height * ratio
                let x:CGFloat = (WebRTC.ViewSize - width) / 2.0
                localRenderView.frame = CGRect(x: x, y: 0, width: width, height: height)
            }else{
                // 縦長
                let width:CGFloat = WebRTC.ViewSize
                let height:CGFloat = width / max(ratio, 0.1)
                let y:CGFloat = (WebRTC.ViewSize - height) / 2.0
                localRenderView.frame = CGRect(x: 0, y: y, width: width, height: height)
            }
        }
    }

    // ▼ static -----

    private static let IceServerUrls = ["stun:stun.l.google.com:19302"]
    private static let factory = RTCPeerConnectionFactory()
    private static var localStream:RTCMediaStream?
    private static var localRenderView = RTCEAGLVideoView()
    private static let _localView = UIView(frame:CGRect(x:10, y:10, width:WebRTC.ViewSize, height:WebRTC.ViewSize))
    private static var localViewDelegate:LocalViewDelegate!

    private static let Margin:CGFloat = 20.0
    static let ViewSize:CGFloat = (windowWidth() - (WebRTC.Margin * 3)) / 2.0

    static func setup(){
        let streamId = WebRTCUtil.idWithPrefix(prefix: "stream")
        localStream = factory.mediaStream(withStreamId: streamId)

        localViewDelegate = LocalViewDelegate(localRenderView: localRenderView)
        _localView.backgroundColor = UIColor.white
        _localView.clipsToBounds = true
        _localView.addSubview(localRenderView)

        setupLocalVideoTrack()
        setupLocalAudioTrack()
    }

    static func disableVideo(){
        localStream?.videoTracks.first?.isEnabled = false
    }

    static func enableVideo(){
        localStream?.videoTracks.first?.isEnabled = true
    }

    static var localView:UIView{
        get{
            return _localView
        }
    }

    private static 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 = true
        }

        localVideoTrack.add(localRenderView)
        localStream?.addVideoTrack(localVideoTrack)
    }

    private static func setupLocalAudioTrack(){
        let localAudioTrack = factory.audioTrack(withTrackId: WebRTCUtil.idWithPrefix(prefix: "audio"))
        localStream?.addAudioTrack(localAudioTrack)
    }

    // ▼ instance ------

    private var remoteStream:RTCMediaStream?
    private var remoteRenderView = RTCEAGLVideoView()
    private let _remoteView = UIView(frame: CGRect(x: 0, y: 0, width: WebRTC.ViewSize, height: WebRTC.ViewSize))

    private var peerConnection:RTCPeerConnection?
    private let callbacks:WebRTCCallbacks

    init(callbacks:WebRTCCallbacks){
        self.callbacks = callbacks
        super.init()

        setupPeerConnection()

        remoteView.clipsToBounds = true
        remoteRenderView.delegate = self
        remoteView.addSubview(remoteRenderView)
    }

    private func setupPeerConnection(){
        let configuration = RTCConfiguration()
        configuration.iceServers = [RTCIceServer(urlStrings: WebRTC.IceServerUrls)]
        peerConnection = WebRTC.factory.peerConnection(with: configuration, constraints: WebRTCUtil.peerConnectionConstraints(), delegate: self)
        peerConnection?.add(WebRTC.localStream!)
    }

    var remoteView:UIView{
        get{
            return _remoteView
        }
    }

    func receiveCandidate(candidate:NSDictionary){
        guard let candidate = candidate as? [AnyHashable:Any]
            , let rtcCandidate = RTCIceCandidate(fromJSONDictionary: candidate) else{
            print("invalid candiate")
            return
        }

        self.peerConnection?.add(rtcCandidate)
    }

    func receiveAnswer(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 を登録
        peerConnection?.setRemoteDescription(sdp, completionHandler: { (error) in

        })
    }

    func receiveOffer(remoteSdp:NSDictionary, callback:@escaping (_ answerSdp:NSDictionary)->()){

        guard let sdpContents = remoteSdp.object(forKey: "sdp") as? String else{
            print("noSDp")
            return;
        }

        // 1. remote SDP を登録
        let remoteSdp = RTCSessionDescription(type: .offer, sdp: sdpContents)
        peerConnection?.setRemoteDescription(remoteSdp, completionHandler: { (error) in

            // 2. answerを作る
            self.peerConnection?.answer(for: WebRTCUtil.answerConstraints(), completionHandler: { (sdp, error) in

                guard let sdp = sdp else{
                    print("can not create sdp")
                    return;
                }

                // 3.ローカルにSDPを登録
                self.peerConnection?.setLocalDescription(sdp, completionHandler: { (error) in

                    // 3. answer を送る
                    guard let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnection?.localDescription) else{
                        print("no localDescription")
                        return ;
                    }

                    callback(localDescription)
                })

            })
        })
    }

    func createOffer(callback:@escaping (_ offerSdp:NSDictionary)->()){

        // 1. offerを作る
        peerConnection?.offer(for: WebRTCUtil.mediaStreamConstraints(), completionHandler: { (description, error) in

            guard let description = description else{
                print("----- no description ----")
                return;
            }

            // 2.ローカルにSDPを登録
            self.peerConnection?.setLocalDescription(description, completionHandler: { (error) in
                // 3. offer を送る
                guard let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnection?.localDescription) else{
                    print("no localDescription")
                    return ;
                }

                callback(localDescription)
            })
        })
    }

    func close(){
        if let localStream = WebRTC.localStream{
            self.peerConnection?.remove(localStream)
        }

        self.peerConnection?.close()
        self.peerConnection = nil
    }

    // 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, didRemove candidates: [RTCIceCandidate]){}
    public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel){}
    public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState){}
    public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState){}

    // for Trickle ice
    public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate){

        if let candidateJson = WebRTCUtil.jsonFromCandidate(candidate: candidate){
            self.callbacks.onIceCandidate(candidateJson)
        }
    }

    public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream){

        self.remoteStream = stream
        if let remoteVideoTrack =  stream.videoTracks.first {
            remoteVideoTrack.add(remoteRenderView)
        }

        DispatchQueue.main.async {
            self.callbacks.onAddedStream(stream, self.remoteView)
        }
    }

    // MARK: RTCEAGLVideoViewDelegate

    func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {

        let ratio:CGFloat = size.width / size.height

        if ratio > 1.0{
            // 横長
            let height:CGFloat = WebRTC.ViewSize
            let width:CGFloat = height * ratio
            let x:CGFloat = (WebRTC.ViewSize - width) / 2.0
            remoteRenderView.frame = CGRect(x: x, y: 0, width: width, height: height)
        }else{
            // 縦長
            let width:CGFloat = WebRTC.ViewSize
            let height:CGFloat = width / max(ratio, 0.1)
            let y:CGFloat = (WebRTC.ViewSize - height) / 2.0
            remoteRenderView.frame = CGRect(x: 0, y: y, width: width, height: height)
        }

    }

}

一個分のコネクション管理

これも、一人の相手ごとに一個インスタンス作る

Connection.swift
import UIKit

typealias ConnectionOnAddedStream = (_ streamWrapper:StreamWrapper)->()

class StreamWrapper:NSObject{

    let stream:RTCMediaStream
    let targetId:String
    let view:UIView

    init(stream:RTCMediaStream, targetId:String, view:UIView){
        self.stream = stream
        self.targetId = targetId
        self.view = view
        super.init()

        let overlay = UIView()
        overlay.frame.size = view.frame.size
        view.addSubview(overlay)

        let labelBg = UIView()
        labelBg.frame = CGRect(x: 0, y: view.frame.size.height - 30, width: view.frame.size.width, height: 30)
        labelBg.backgroundColor = UIColor.white
        labelBg.alpha = 0.8
        overlay.addSubview(labelBg)

        let label = UILabel()
        label.frame = CGRect(x: 0, y: view.frame.size.height - 30, width: view.frame.size.width, height: 30)
        label.textAlignment = .center
        label.text = targetId
        label.textColor = UIColor.black
        overlay.addSubview(label)
    }
}

class Connection: NSObject {

    private let onAddedStream:ConnectionOnAddedStream
    private var webRtc:WebRTC!
    private let myId:String
    let targetId:String

    init(myId:String, targetId:String, onAddedStream:@escaping ConnectionOnAddedStream){
        self.myId = myId
        self.targetId = targetId
        self.onAddedStream = onAddedStream

        super.init()

        webRtc = WebRTC(callbacks: (
            onIceCandidate: {(iceCandidate:NSDictionary) -> Void in

                let jsonData = try! JSONSerialization.data(withJSONObject: iceCandidate, options: [])
                let jsonStr = String(bytes: jsonData, encoding: .utf8)!

                let wamp = Wamp.sharedInstance
                let topic = wamp.endpointCandidate(targetId: targetId)
                wamp.session.publish(topic, options: [:], args: [jsonStr], kwargs: [:])
            }
            , onAddedStream: {(stream:RTCMediaStream, view:UIView) -> Void in
                let streamWrapper = StreamWrapper(stream: stream, targetId: targetId, view:view)
                self.onAddedStream(streamWrapper)
            }
            , onRemoveStream: {(stream:RTCMediaStream) -> Void in

            }
        ))

        // for tricke ice
        subscribeCandidate()
    }

    private func subscribeCandidate(){
        let wamp = Wamp.sharedInstance
        let candidateTopic = wamp.endpointCandidate(targetId: myId)

        Wamp.sharedInstance.session.subscribe(candidateTopic, onSuccess: { (subscription) in
        }, onError: { (results, error) in
        }) { (results, args, kwArgs) in

            guard let candidateStr = args?.first as? String else{
                print(args?.first)
                print("no candidate")
                return
            }

            let data = candidateStr.data(using: String.Encoding.utf8)!
            let candidate = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary

            self.webRtc.receiveCandidate(candidate: candidate)
        }
    }

    deinit {
        print("connection deinit")
    }

    func publishAnswer(offerSdp:NSDictionary){
        webRtc.receiveOffer(remoteSdp: offerSdp) { (answerSdp) in
            let wamp = Wamp.sharedInstance
            let topic = wamp.endpointAnswer(targetId: self.targetId)

            let jsonData = try! JSONSerialization.data(withJSONObject: answerSdp, options: [])
            let jsonStr = String(bytes: jsonData, encoding: .utf8)!

            Wamp.sharedInstance.session.publish(topic, options: [:], args: [self.myId, jsonStr], kwargs: [:])
        }
    }

    func publishOffer(){
        webRtc.createOffer { (offerSdp) in
            let wamp = Wamp.sharedInstance
            let topic = wamp.endpointOffer(targetId: self.targetId)

            let jsonData = try! JSONSerialization.data(withJSONObject: offerSdp, options: [])
            let jsonStr = String(bytes: jsonData, encoding: .utf8)!

            wamp.session.publish(topic, options: [:], args: [self.myId, jsonStr], kwargs: [:])
        }
    }

    func receiveAnswer(sdp:NSDictionary){
        webRtc.receiveAnswer(remoteSdp: sdp)
    }

    func receiveCnadidate(candidate:NSDictionary){

    }

    func close(){
        print("close connection")
        webRtc.close()
    }

}

アプリケーション

roomKey はとりあえず固定

ViewController.swift
import UIKit
import FirebaseDatabase

import UIKit

extension String {
    static func getRandomStringWithLength(length: Int) -> String {

        let alphabet = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        let upperBound = UInt32(alphabet.characters.count)

        return String((0..<length).map { _ -> Character in
            return alphabet[alphabet.index(alphabet.startIndex, offsetBy: Int(arc4random_uniform(upperBound)))]
        })
    }
}

class ViewController: UIViewController {

    private let wamp = Wamp.sharedInstance
    private var connectionList:[Connection] = []
    private var streamWrapperList:[StreamWrapper] = []

    private let remoteLayer = UIView(frame: windowFrame())
    private let localLayer = UIView(frame: CGRect(x: 0, y: windowHeight()-WebRTC.ViewSize - 20, width: windowWidth(), height: WebRTC.ViewSize + 20))
    private var roomKey:String!
    private let userId = String.getRandomStringWithLength(length: 8)
    private let toggleButton = UIButton()

    deinit {
        print("ViewController deinit")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        setupRoom()
        setupWamp()
        setupStream()

        Wamp.sharedInstance.connect()

        view.addSubview(remoteLayer)
        view.addSubview(localLayer)

        localLayer.backgroundColor = UIColor.gray
        localLayer.addSubview(WebRTC.localView)

        let labelX = WebRTC.ViewSize + 20.0
        let labelWidth = windowWidth() - labelX - 10
        let label = UILabel(frame: CGRect(x: labelX, y: 10, width: labelWidth, height: 50))
        label.numberOfLines = 2
        label.font = UIFont.systemFont(ofSize: 20.0)
        label.textColor = UIColor.white
        label.text = "You\n(\(self.userId))"
        label.sizeToFit()
        localLayer.addSubview(label)

        let y:CGFloat = label.frame.size.height + 20

        toggleButton.frame = CGRect(x: labelX, y: y, width: labelWidth, height: 44)
        toggleButton.backgroundColor = UIColor.white
        toggleButton.layer.cornerRadius = 5
        toggleButton.clipsToBounds = true
        toggleButton.setTitleColor(UIColor.gray, for: .normal)
        toggleButton.setTitle("Stop", for: .normal)
        toggleButton.setTitle("Play", for: .selected)
        toggleButton.addTarget(self, action: #selector(ViewController.tapToggle), for: .touchUpInside)
        localLayer.addSubview(toggleButton)
    }

    private dynamic func tapToggle(){

        toggleButton.isSelected = !toggleButton.isSelected

        let active = !toggleButton.isSelected
        if active{
            WebRTC.enableVideo()
        }else{
            WebRTC.disableVideo()
        }
    }

    private func setupStream(){
        WebRTC.setup()
    }

    private func setupRoom(){
        let roomKey:String? = "-Kr-JqhdoZ1YtdeO0-9r"

        if let roomKey = roomKey{
            self.roomKey = roomKey
            return
        }

        let ref = Database.database().reference().child("rooms")
        let roomRef = ref.childByAutoId()
        self.roomKey = roomRef.key

        print("roomKey:\(roomRef.key)")
    }

    private func setupWamp(){

        let wamp = Wamp.sharedInstance
        Wamp.sharedInstance.setup(roomKey: roomKey, userId: userId
        , callbacks: (
            onOpen:{() -> Void in
                print("onOpen")

                let topic = wamp.endpointCallme()
                wamp.session.publish(topic, options: [:], args: [self.userId], kwargs: [:])
            }
            , onReceiveOffer:{(targetId:String, sdp:NSDictionary) -> Void in
                print("onReceiveOffer")
                let connection = self.createConnection(targetId: targetId)
                connection.publishAnswer(offerSdp: sdp)
            }
            , onReceiveAnswer:{(targetId:String, sdp:NSDictionary) -> Void in
                print("onReceiveAnswer")
                guard let connection = self.findConnection(targetId: targetId) else{
                    return
                }
                connection.receiveAnswer(sdp: sdp)
            }

            , onReceiveCallme:{(targetId:String) -> Void in
                print("onReceivCallme")
                let connection = self.createConnection(targetId:targetId)
                connection.publishOffer()
            }
            , onCloseConnection:{(targetId:String) -> Void in
                print("onCloseConnection")

                // removeConnection
                guard let removeIndex = self.connectionList.index(where: { (row) -> Bool in
                    return row.targetId == targetId
                }) else{
                    return
                }

                let connection = self.connectionList.remove(at: removeIndex)
                connection.close()

                // removeView
                guard let streamIndex = self.streamWrapperList.index(where: { (row) -> Bool in
                    return row.targetId == targetId
                }) else{
                    return;
                }

                let streamWrapper = self.streamWrapperList.remove(at: streamIndex)
                streamWrapper.view.removeFromSuperview()

                self.calcRemoteViewPosition()
            }
       ))
    }

    private func createConnection(targetId:String)->Connection{
        let connection = Connection(myId: userId, targetId: targetId) { (streamWrapper) in
            print("onAeedStream")

            self.remoteLayer.addSubview(streamWrapper.view)
            self.streamWrapperList.append(streamWrapper)

            self.calcRemoteViewPosition()

        }
        connectionList.append(connection)
        return connection
    }

    private func findConnection(targetId:String)->Connection?{
        for i in 0..<connectionList.count{
            let connection = connectionList[i]
            if(connection.targetId == targetId){
                return connection
            }
        }

        print("not found connection")
        return nil
    }

    private func calcRemoteViewPosition(){
        var y:CGFloat = 20.0
        var x:CGFloat = 20

        for i in 0..<streamWrapperList.count{
            let view = streamWrapperList[i].view
            view.frame.origin = CGPoint(x: x, y: y)

            if x + view.frame.size.width > windowWidth(){
                x = 20
                y = y + view.frame.size.height
                view.frame.origin = CGPoint(x: x, y: y)
            }
            x = x + view.frame.size.width + 20
        }
    }

}

  • Android 複数接続
  • 接続切れた時が取れない
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2