alongubkin / phonertc

WebRTC for Cordova apps - No longer active
http://phonertc.io
Apache License 2.0
866 stars 305 forks source link

swift 2.3 升级到 swift 3.0 #235

Open wolfmanwoking opened 7 years ago

wolfmanwoking commented 7 years ago

我已经将 Config 、 PCObserver 、PhoneRTCPlugin、Session、SessionDescriptionDelegate、SJProgressHUD 等 swift 文件升级到 swift 3.0 了。请更新您的文件,以便项目在未来做好升级。

谢谢您的观看。

Config.swift

import Foundation

struct TurnConfig { var host: String var username: String var password: String

// ivan - write - 7/03
init(){
    self.host = "";
    self.username = "";
    self.password = "";
}

// ivan - write - 7/03
init( host:String, username:String, password:String ){
    self.host = host;
    self.username = username;
    self.password = password;
}

}

struct StreamsConfig { var audio: Bool var video: Bool

// ivan - write - 7/03
init() {
    self.audio = true;
    self.video = true;
}

// ivan - write - 7/03
init( audio:Bool, video:Bool ){
    self.audio = audio;
    self.video = video;
}

}

class SessionConfig { var isInitiator: Bool var turn: TurnConfig var streams: StreamsConfig

// ivan - write - 7/03
init(isInitiator:Bool, turn:TurnConfig, streams:StreamsConfig ){
    self.isInitiator = isInitiator;
    self.turn = TurnConfig( host: turn.host, username: turn.username, password: turn.password  );
    self.streams = StreamsConfig( audio: streams.audio, video: streams.video );
}

init( isInitiator:Bool, turn:[String: AnyObject], streams:[String: AnyObject] ){
    self.isInitiator = isInitiator;

    self.turn = TurnConfig( host: (turn["host"] as? String)!, username: (turn["username"] as? String)!, password: (turn["password"] as? String)!  );

    self.streams = StreamsConfig( audio: streams["audio"] as! Bool, video: streams["video"] as! Bool );

}

init(_ data: [String: AnyObject] ) {
    self.isInitiator = data["isInitiator"] as! Bool

    let turnObject: [String: AnyObject] = data["turn"] as! [String: AnyObject]
    self.turn = TurnConfig(
        host: turnObject["host"] as! String,
        username: turnObject["username"] as! String,
        password: turnObject["password"] as! String
    )

    let streamsObject: [String: AnyObject] = data["streams"] as! [String: AnyObject];
    self.streams = StreamsConfig(
        audio: streamsObject["audio"] as! Bool,
        video: streamsObject["video"] as! Bool
    )
}

}

class VideoConfig { var container: VideoLayoutParams var local: VideoLayoutParams?

// ivan - write - 7/03
init( container:VideoLayoutParams, local:VideoLayoutParams? ) {

    self.container = VideoLayoutParams( x:container.x, y:container.y, width: container.width, height: container.height );

    if local != nil {
        self.local = VideoLayoutParams( x:local!.x, y:local!.y, width: local!.width, height: local!.height );
    }

}

// ivan -- write - 7/7
init( data: [String : AnyObject]) {
    let containerParams: [String : AnyObject ] = data["containerParams"] as! [String : AnyObject ]
    let localParams: [String : AnyObject ]? = data["local"] as? [String : AnyObject ]

    self.container = VideoLayoutParams( containerParams )

    if localParams != nil {
        self.local = VideoLayoutParams( localParams! )
    }
}

// ivan -- write - 7/7
init(_ data: [String : AnyObject]) {
    let containerParams: [String : AnyObject ] = data["containerParams"] as! [String : AnyObject ]
    let localParams: [String : AnyObject ]? = data["local"] as? [String : AnyObject ]

    self.container = VideoLayoutParams( containerParams )

    if localParams != nil {
        self.local = VideoLayoutParams( localParams! )
    }
}

}

class VideoLayoutParams { var x, y, width, height: Int

init(x: Int, y: Int, width: Int, height: Int) {
    self.x = x
    self.y = y
    self.width = width
    self.height = height
}

// ivan -- write - 7/7
init(_ data: [String : AnyObject ]) {
    let position: [Int] = data["position"] as! [Int]
    self.x = position[0]
    self.y = position[1]

    let size: [Int] = data["size"] as! [Int]
    self.width = size[0]
    self.height = size[1]
}

}


PCObserver.swift

import Foundation

class PCObserver : NSObject, RTCPeerConnectionDelegate { var session: Session

init(session: Session) {
    self.session = session
}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    addedStream stream: RTCMediaStream!) {
    print("PCO onAddStream.")

    DispatchQueue.main.async {
        if stream.videoTracks.count > 0 {
            self.session.addVideoTrack(stream.videoTracks[0] as! RTCVideoTrack)
        }
    }

    self.session.sendMessage(
        "{\"type\": \"__answered\"}".data(using: String.Encoding.utf8)!)
}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    removedStream stream: RTCMediaStream!) {
    print("PCO onRemoveStream.")

}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    iceGatheringChanged newState: RTCICEGatheringState) {
    print("PCO onIceGatheringChange. \(newState)")

}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    iceConnectionChanged newState: RTCICEConnectionState)
{
    print("PCO onIceConnectionChange. \(newState)")

}

func peerConnection(_ peerConnection: RTCPeerConnection,
    gotICECandidate candidate: RTCICECandidate ) {
    let getSdpMid : String = candidate.sdpMid as String;
    let getSdpLineIndex : Int = candidate.sdpMLineIndex as Int;
    let getSdp : String = candidate.sdp as String;

    print("PCO -- onICECandidate --Mid: \(getSdpMid) \n Index: \(getSdpLineIndex)   \n Sdp:\(getSdp) ")

    let json: [String: AnyObject ] = [
        "type": "candidate" as AnyObject,
        "label": candidate.sdpMLineIndex as AnyObject,
        "id": candidate.sdpMid as AnyObject,
        "candidate": candidate.sdp as AnyObject
    ]

    let data: Data?
    do {
        data = try JSONSerialization.data(withJSONObject: json,
                    options: JSONSerialization.WritingOptions())
    } catch let error as NSError {
        print( "error: \(error)" );
        data = nil
    }

    self.session.sendMessage(data!)
}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    signalingStateChanged stateChanged: RTCSignalingState) {
    print("PCO onSignalingStateChange: \(stateChanged)")
}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    didOpen dataChannel: RTCDataChannel!) {
    print("PCO didOpenDataChannel.")
}

func peerConnectionOnError(_ peerConnection: RTCPeerConnection!) {
    print("PCO onError.")
}

func peerConnection(onRenegotiationNeeded peerConnection: RTCPeerConnection!) {
    print("PCO onRenegotiationNeeded.")
    // TODO: Handle this
}

}


PhoneRTCPlugin.swift

import Foundation import AVFoundation import AudioToolbox

@objc(PhoneRTCPlugin) class PhoneRTCPlugin : CDVPlugin ,UIAlertViewDelegate{ var sessions: [String: Session]! var peerConnectionFactory: RTCPeerConnectionFactory!

var videoConfig: VideoConfig?
var videoCapturer: RTCVideoCapturer?
var videoSource: RTCVideoSource?
var localVideoView: RTCEAGLVideoView?
var remoteVideoViews: [VideoTrackViewPair]!
var camera: String?
var scaleView:RTCEAGLVideoView?

var localVideoTrack: RTCVideoTrack?
var localAudioTrack: RTCAudioTrack?

func createSessionObject(_ command: CDVInvokedUrlCommand) {

    if let sessionKey = command.argument(at: 0) as? String {

        if let args: [String: AnyObject] = command.argument(at: 1) as! [String: AnyObject]? {

            let getIsInitiator: Bool = args["isInitiator"] as! Bool;
            let getTurn: [String:AnyObject] = args["turn"] as! [String:AnyObject];

            let getArgsStreams: [String:AnyObject] = args["streams"] as! [String:AnyObject];

            let config:SessionConfig = SessionConfig(isInitiator: getIsInitiator, turn: getTurn , streams:getArgsStreams );

            self.sessions[sessionKey] = Session(plugin: self,
                                                    peerConnectionFactory: peerConnectionFactory,
                                                    config: config,
                                                    callbackId: command.callbackId,
                                                    sessionKey: sessionKey )

        }
    }
}

func call(_ command: CDVInvokedUrlCommand) {
    let args: [String:AnyObject] = command.argument(at: 0 ) as! [String:AnyObject];
    if let sessionKey = args["sessionKey"] as? String {

        DispatchQueue.main.async() {
            if let session = self.sessions[sessionKey] {
                session.call()
            }
        }
    }
}

func receiveMessage(_ command: CDVInvokedUrlCommand) {
    let args: [String:AnyObject] = command.argument(at: 0 ) as! [String:AnyObject];

    if let sessionKey = args["sessionKey"] as? String {
        if let message = args["message"] as? String {
            if let session = self.sessions[sessionKey] {
                DispatchQueue.global().async() {
                    session.receiveMessage(message)
                }
            }
        }
    }
}

func renegotiate(_ command: CDVInvokedUrlCommand) {
    let args: [String:AnyObject] = command.argument(at: 0) as! [String:AnyObject];
    if let sessionKey = args["sessionKey"] as? String {
        if let config: SessionConfig = args["config"] as? SessionConfig {

            DispatchQueue.main.async() {
                if let session = self.sessions[sessionKey] {
                    session.config = config;
                    session.createOrUpdateStream()
                }
            }
        }
    }
}

func disconnect(_ command: CDVInvokedUrlCommand) {
    let args: [String: AnyObject] = command.argument(at: 0 ) as! [String: AnyObject];
    if let sessionKey = args["sessionKey"] as? String {

        DispatchQueue.global().async() {
            if (self.sessions[sessionKey] != nil) {
                self.sessions[sessionKey]!.disconnect(true)
            }
        }
    }
}

func sendMessage(_ callbackId: String, message: NSData) {
    let json = (try! JSONSerialization.jsonObject(with: message as Data,
        options: JSONSerialization.ReadingOptions.mutableLeaves)) as! NSDictionary
    let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: json as [NSObject : AnyObject] )

    pluginResult?.setKeepCallbackAs(true);
    self.commandDelegate?.send(pluginResult, callbackId:callbackId)

}

func setVideoView(_ command: CDVInvokedUrlCommand) {
    let config: [String : AnyObject ] = command.argument(at: 0) as! [String : AnyObject ]

    DispatchQueue.main.async() {

        let videoConfig:VideoConfig = VideoConfig( data: config )
        if videoConfig.container.width == 0 || videoConfig.container.height == 0 {
            return
        }

        self.videoConfig = videoConfig
        self.camera = config["camera"] as? String

        if self.videoConfig!.local != nil {
            if self.localVideoTrack == nil {
                if(self.camera == "Front" || self.camera == "Back") {
                    self.initLocalVideoTrack(self.camera!)
                }else {
                    self.initLocalVideoTrack()
                }
            }

            if self.videoConfig!.local == nil {
                if self.localVideoView != nil {
                    self.localVideoView!.isHidden = true
                    self.localVideoView!.removeFromSuperview()
                    self.localVideoView = nil
                }
            } else {
                let params = self.videoConfig!.local!
                if self.localVideoView != nil {
                    self.localVideoView!.frame = CGRect( x:CGFloat(params.x + self.videoConfig!.container.x),
                                                         y:CGFloat(params.y + self.videoConfig!.container.y),
                                                         width:CGFloat(params.width),
                                                         height:CGFloat(params.height)
                                            )
                } else {
                    // otherwise, create the local video view
                    self.localVideoView = self.createVideoView(params)
                    self.localVideoTrack!.add(self.localVideoView!)
                }
            }

            self.refreshVideoContainer()

        }
        let gesture = UIPanGestureRecognizer(target:self, action:#selector(PhoneRTCPlugin.handlePanGesture(sender:)));
        self.localVideoView!.addGestureRecognizer(gesture)
    }
}

func handlePanGesture(sender: UIPanGestureRecognizer){
    let transX = sender.location(in: self.webView).x
    let transY = sender.location(in: self.webView).y
    print(transX,transY)
    sender.view?.center = CGPoint(x: transX, y:transY )

}

func hideVideoView(_ command: CDVInvokedUrlCommand) {
    DispatchQueue.main.async() {
        if (self.localVideoView != nil) {
            self.localVideoView!.isHidden = true;
        }

        for remoteVideoView in self.remoteVideoViews {
            remoteVideoView.videoView.isHidden = true;
        }
    }
}

func showVideoView(_ command: CDVInvokedUrlCommand) {
    DispatchQueue.main.async() {
        if (self.localVideoView != nil) {
            self.localVideoView!.isHidden = false;
        }

        for remoteVideoView in self.remoteVideoViews {
            remoteVideoView.videoView.isHidden = false;
        }
    }
}

func createVideoView(_ params: VideoLayoutParams? = nil) -> RTCEAGLVideoView {
    if params != nil {
        let frame = CGRect(x:
            CGFloat(params!.x + self.videoConfig!.container.x),
                           y:
            CGFloat(params!.y + self.videoConfig!.container.y),
                           width:
            CGFloat(params!.width),
                           height:
            CGFloat(params!.height)
        )

        scaleView = RTCEAGLVideoView(frame: frame)
    } else {
        scaleView = RTCEAGLVideoView()

        let buttonSpeaker = UIButton(frame: CGRect(x:
            CGFloat(self.videoConfig!.container.width-40),
                                                   y:
            CGFloat(self.videoConfig!.container.height-40),
                                                   width:
            CGFloat(30),
                                                   height:
            CGFloat(30)
            ))
        buttonSpeaker.addTarget(self, action: #selector(PhoneRTCPlugin.tapSpeakerBtn(sender:)), for: UIControlEvents.touchUpInside)

        DispatchQueue(label:"my_queue").async() {
            DispatchQueue.main.async() {
                try? AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.speaker);

            }
        }
    }

    self.webView!.addSubview(scaleView!)
    return scaleView!
}

func tapSpeakerBtn(sender:UIButton){
    if (sender.isSelected == true) {

        sender.isSelected=false
        SJProgressHUD.showInfo("扬声器已关闭", autoRemove:true)

        try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)

    }else{

        sender.isSelected=true
        SJProgressHUD.showInfo("扬声器已打开", autoRemove:true)
        try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)

    }

}

func initLocalAudioTrack() {

    localAudioTrack = peerConnectionFactory.audioTrack(withID: "ARDAMSa0")
    DispatchQueue(label:"my_queue").async() {
        DispatchQueue.main.async() {
            try? AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.speaker);

        }
    }

}

func initLocalVideoTrack() {
    var cameraID: String?
    for captureDevice in AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) {
        if (captureDevice as AnyObject).position == AVCaptureDevicePosition.front {
            cameraID = (captureDevice as AnyObject).localizedName
        }
    }

    self.videoCapturer = RTCVideoCapturer(deviceName: cameraID)
    self.videoSource = self.peerConnectionFactory.videoSource(
        with: self.videoCapturer,
        constraints: RTCMediaConstraints()
    )

    self.localVideoTrack = self.peerConnectionFactory
        .videoTrack(withID: "ARDAMSv0", source: self.videoSource)

}

func initLocalVideoTrack(_ camera: String) {
    NSLog("PhoneRTC: initLocalVideoTrack(camera: String) invoked")
    var cameraID: String?
    for captureDevice in AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) {
        if (captureDevice as AnyObject).position == AVCaptureDevicePosition.front {
            if camera == "Front"{
                cameraID = (captureDevice as AnyObject).localizedName
            }
        }
        if (captureDevice as AnyObject).position == AVCaptureDevicePosition.back {
            if camera == "Back"{
                cameraID = (captureDevice as AnyObject).localizedName
            }
        }
    }

    self.videoCapturer = RTCVideoCapturer(deviceName: cameraID)
    self.videoSource = self.peerConnectionFactory.videoSource(
        with: self.videoCapturer,
        constraints: RTCMediaConstraints()
    )

    self.localVideoTrack = self.peerConnectionFactory
        .videoTrack(withID: "ARDAMSv0", source: self.videoSource)
}

func addRemoteVideoTrack(_ videoTrack: RTCVideoTrack) {
    if self.videoConfig == nil {
        return
    }
    let videoView = createVideoView()

    videoTrack.add(videoView)
    self.remoteVideoViews.append(VideoTrackViewPair(videoView: videoView, videoTrack: videoTrack))

    refreshVideoContainer()

    if self.localVideoView != nil {
        self.webView!.addSubview(self.localVideoView!)
    }

}

func removeRemoteVideoTrack(_ videoTrack: RTCVideoTrack) {
    DispatchQueue.main.async() {

        if (self.localVideoView != nil) {
            self.localVideoView!.isHidden = true
            self.localVideoView!.removeFromSuperview()
            self.localVideoView = nil
        }
        for i in 0 ..< self.remoteVideoViews.count {
            let pair = self.remoteVideoViews[i]
            if pair.videoTrack == videoTrack {
                pair.videoView.isHidden = true
                pair.videoView.removeFromSuperview()

                self.remoteVideoViews.remove(at: i)
                self.refreshVideoContainer()
                return
            }
        }
    }
}

func refreshVideoContainer() {
    let n = self.remoteVideoViews.count

    if n == 0 {
        return
    }

    let rows = n < 9 ? 2 : 3
    let videosInRow = n == 2 ? 2 : Int(ceil(Float(n) / Float(rows)))

    let videoSize = Int(Float(self.videoConfig!.container.width) / Float(videosInRow))
    let actualRows = Int(ceil(Float(n) / Float(videosInRow)))

    var y = getCenter(actualRows, videoSize: videoSize, containerSize: self.videoConfig!.container.height)
        + self.videoConfig!.container.y

    var videoViewIndex = 0
    var row = 0

    while row < rows && videoViewIndex < n {

        var x = getCenter( row < row - 1 || n % rows == 0 ?
            videosInRow : n - (min(n, videoViewIndex + videosInRow) - 1),
                          videoSize: videoSize,
                          containerSize: self.videoConfig!.container.width)
            + self.videoConfig!.container.x

        var video = 0;
        while video < videosInRow && videoViewIndex < n {

            let pair = self.remoteVideoViews[videoViewIndex];
            videoViewIndex += 1;

            pair.videoView.frame = CGRect(x:
                CGFloat(x),
                                          y:
                CGFloat(y),
                                          width:
                CGFloat(videoSize),
                                          height:
                CGFloat(videoSize)
            )

            x += Int(videoSize)

            video += 1
        }

        y += Int(videoSize)

        row += 1

    }

}

func getCenter(_ videoCount: Int, videoSize: Int, containerSize: Int) -> Int {
    return lroundf(Float(containerSize - videoSize * videoCount) / 2.0)
}

func onSessionDisconnect(_ sessionKey: String) {
    self.sessions.removeValue(forKey: sessionKey)

    if self.sessions.count == 0 {

        DispatchQueue.main.sync() {
            if (self.localVideoView != nil) {
                self.localVideoView!.isHidden = true
                self.localVideoView!.removeFromSuperview()
                self.localVideoView = nil
            }
        }

        self.localVideoTrack = nil
        self.localAudioTrack = nil

        self.videoSource = nil
        self.videoCapturer = nil

    }
}

override func pluginInitialize() {
    self.sessions = [:];
    self.remoteVideoViews = [];

    peerConnectionFactory = RTCPeerConnectionFactory()
    RTCPeerConnectionFactory.initializeSSL()

}

}

struct VideoTrackViewPair { var videoView: RTCEAGLVideoView var videoTrack: RTCVideoTrack }


Session.swift

import Foundation

class Session { var plugin: PhoneRTCPlugin var config: SessionConfig var constraints: RTCMediaConstraints var peerConnection: RTCPeerConnection! var pcObserver: PCObserver! var queuedRemoteCandidates: [RTCICECandidate]? var peerConnectionFactory: RTCPeerConnectionFactory var callbackId: String var stream: RTCMediaStream? var videoTrack: RTCVideoTrack? var sessionKey: String

init(plugin: PhoneRTCPlugin, peerConnectionFactory: RTCPeerConnectionFactory, config: SessionConfig, callbackId: String, sessionKey: String ) {

    self.plugin = plugin
    self.queuedRemoteCandidates = []
    self.config = config
    self.peerConnectionFactory = peerConnectionFactory
    self.callbackId = callbackId
    self.sessionKey = sessionKey

    self.constraints = RTCMediaConstraints(
        mandatoryConstraints: [
            RTCPair(key: "OfferToReceiveAudio", value: "true"),
            RTCPair(key: "OfferToReceiveVideo", value:
                self.plugin.videoConfig == nil ? "false" : "true"),
        ],

        optionalConstraints: [
            RTCPair(key: "internalSctpDataChannels", value: "true"),
            RTCPair(key: "DtlsSrtpKeyAgreement", value: "true")
        ]
    )
}

func call() {
    var iceServers: [RTCICEServer] = []
    iceServers.append(RTCICEServer(
        uri: URL(string: "stun:stun.l.google.com:19302"),
        username: "",
        password: ""))

    iceServers.append(RTCICEServer(
        uri: URL(string: self.config.turn.host),
        username: self.config.turn.username,
        password: self.config.turn.password))

    self.pcObserver = PCObserver(session: self)
    self.peerConnection =
        peerConnectionFactory.peerConnection(withICEServers: iceServers,
            constraints: self.constraints,
            delegate: self.pcObserver)

    createOrUpdateStream()

    if self.config.isInitiator {
        self.peerConnection.createOffer(with: SessionDescriptionDelegate(session: self),
            constraints: constraints)
    }
}

func createOrUpdateStream() {
    if self.stream != nil {
        self.peerConnection.remove(self.stream)
        self.stream = nil
    }
    self.stream = peerConnectionFactory.mediaStream(withLabel: "ARDAMS")

    if self.config.streams.audio {
        // init local audio track if needed
        if self.plugin.localAudioTrack == nil {
            self.plugin.initLocalAudioTrack()
        }

        self.stream!.addAudioTrack(self.plugin.localAudioTrack!)
    }

    if self.config.streams.video {
        if self.plugin.localVideoTrack == nil {
            self.plugin.initLocalVideoTrack()
        }

        self.stream!.addVideoTrack(self.plugin.localVideoTrack!)
    }

    self.peerConnection.add( self.stream )
}

func receiveMessage(_ message: String) {
    var error : NSError?
    let data : [String:AnyObject]?
    do {
        data = try JSONSerialization.jsonObject( with: message.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions() ) as? [String:AnyObject];

    } catch let error1 as NSError {
        error = error1
        data = nil
    }

    print( "data: \(String(describing: data))" );

    if let object: [String:AnyObject] = data {
        if let type = object["type"] as? String {
            switch type {
            case "candidate":
                let mid: String = object["id"] as! String

                let sdpLineIndex: Int = (object["label"] as? Int)!

                let sdp: String = object["candidate"] as! String

                let candidate = RTCICECandidate(
                    mid: mid,
                    index: sdpLineIndex,
                    sdp: sdp
                )

                if self.queuedRemoteCandidates != nil {
                    self.queuedRemoteCandidates?.append(candidate!)
                } else {
                    self.peerConnection.add(candidate)
                }

                case "offer", "answer":
                    if let sdpString = object["sdp"] as? String {
                        let sdp = RTCSessionDescription(type: type, sdp: self.preferISAC(sdpString))
                        self.peerConnection.setRemoteDescriptionWith(SessionDescriptionDelegate(session: self),
                                                                             sessionDescription: sdp)
                    }
                case "bye":
                    self.disconnect(false)
                default:
                    print("Invalid message \(message)")
            }
        }
    } else {
        if let parseError = error {
            print("There was an error parsing the client message: \(parseError.localizedDescription)")
        }
        return
    }
}

func disconnect(_ sendByeMessage: Bool) {

    if self.videoTrack != nil {
        self.removeVideoTrack(self.videoTrack!)
    }

    if self.peerConnection != nil {
        if sendByeMessage {
            let json: [String : String] = [
                "type": "bye"
            ]

            let data = try? JSONSerialization.data(withJSONObject: json,
                options: JSONSerialization.WritingOptions())

            self.sendMessage(data!)
        }

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

    let json: [String : String] = [
        "type": "__disconnected"
    ]

    let data = try? JSONSerialization.data(withJSONObject: json,
        options: JSONSerialization.WritingOptions())

    self.sendMessage(data!)

    self.plugin.onSessionDisconnect(self.sessionKey)
}

func addVideoTrack(_ videoTrack: RTCVideoTrack) {
    self.videoTrack = videoTrack
    self.plugin.addRemoteVideoTrack(videoTrack)
}

func removeVideoTrack(_ videoTrack: RTCVideoTrack) {
    self.plugin.removeRemoteVideoTrack(videoTrack)
}

func preferISAC(_ sdpDescription: String) -> String {

    var mLineIndex = -1
    var isac16kRtpMap: String?

    let origSDP = sdpDescription.replacingOccurrences(of: "\r\n", with: "\n")
    var lines = origSDP.components(separatedBy: "\n")
    let isac16kRegex = try? NSRegularExpression(
        pattern: "^a=rtpmap:(\\d+) ISAC/16000[\r]?$",
        options: NSRegularExpression.Options())

    var i = 0;
    while  (i < lines.count) && (mLineIndex == -1 || isac16kRtpMap == nil) {
        let line = lines[i]
        if line.hasPrefix("m=audio ") {
            mLineIndex = i
            i += 1
            continue
        }
        isac16kRtpMap = self.firstMatch(isac16kRegex!, string: line)
        i += 1
    }

    if mLineIndex == -1 {
        print("No m=audio line, so can't prefer iSAC")
        return origSDP
    }

    if isac16kRtpMap == nil {
        print("No ISAC/16000 line, so can't prefer iSAC")
        return origSDP
    }

    let origMLineParts = lines[mLineIndex].components(separatedBy: " ")

    var newMLine: [String] = []
    var origPartIndex = 0;

    newMLine.append(origMLineParts[origPartIndex])
    origPartIndex+=1;

    newMLine.append(origMLineParts[origPartIndex])
    origPartIndex+=1;

    newMLine.append(origMLineParts[origPartIndex])
    origPartIndex+=1;

    newMLine.append(isac16kRtpMap!)

    while origPartIndex < origMLineParts.count {
        if isac16kRtpMap != origMLineParts[origPartIndex] {
            newMLine.append(origMLineParts[origPartIndex])
        }
        origPartIndex += 1
    }

    lines[mLineIndex] = newMLine.joined(separator: " ")
    return lines.joined(separator: "\r\n")
}

func firstMatch(_ pattern: NSRegularExpression, string: String) -> String? {
    let nsString = string as NSString

    let result = pattern.firstMatch(in: string,
        options: NSRegularExpression.MatchingOptions(),
        range: NSMakeRange(0, nsString.length))

    if result == nil {
        return nil
    }

    return nsString.substring(with: result!.rangeAt(1))
}

func sendMessage(_ message: Data) {
    self.plugin.sendMessage( self.callbackId, message: message as NSData)
}

}


SessionDescriptionDelegate.swift

import Foundation

class SessionDescriptionDelegate : UIResponder, RTCSessionDescriptionDelegate { var session: Session

init(session: Session) {
    self.session = session
}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    didCreateSessionDescription originalSdp: RTCSessionDescription!, error: Error!) {
    if error != nil {
        print("SDP OnFailure: \(error)")
        return
    }

    let sdp = RTCSessionDescription(
        type: originalSdp.type,
        sdp: self.session.preferISAC(originalSdp.description)
    )

    self.session.peerConnection.setLocalDescriptionWith(self, sessionDescription: sdp)

    DispatchQueue.main.async {
        let json: [String: String ] = [
            "type": sdp!.type,
            "sdp": sdp!.description
        ]

        let data: Data?
        do {
            data = try JSONSerialization.data(withJSONObject: json,
                            options: JSONSerialization.WritingOptions())
        } catch let error as NSError {
            print( "error: \(error)" );

            data = nil
        } catch {

            fatalError()
        }

        self.session.sendMessage(data!)
    }
}

func peerConnection(_ peerConnection: RTCPeerConnection!,
    didSetSessionDescriptionWithError error: Error!) {
    if error != nil {
        print("SDP OnFailure: \(error)")
        return
    }

    DispatchQueue.main.async {
        if self.session.config.isInitiator {
            if self.session.peerConnection.remoteDescription != nil {
                print("SDP onSuccess - drain candidates")
                self.drainRemoteCandidates()
            }
        } else {
            if self.session.peerConnection.localDescription != nil {
                self.drainRemoteCandidates()
            } else {
                self.session.peerConnection.createAnswer(with: self,
                    constraints: self.session.constraints)
            }
        }
    }
}

func drainRemoteCandidates() {
    if self.session.queuedRemoteCandidates != nil {
        for candidate in self.session.queuedRemoteCandidates! {
            self.session.peerConnection.add(candidate)
        }
        self.session.queuedRemoteCandidates = nil
    }
}

}


SJProgressHUD.swift

// // SJProgressHUD.swift // SJProgressHUD // // Created by king on 16/4/10. // Copyright © 2016年 king. All rights reserved. //

import UIKit

enum ShowType { case success case error case info }

extension SJProgressHUD {

/**
 加载动画图片

 - parameter images:       图片数组
 - parameter timeInterval: 动画每次执行时间
 */
static func showWaitingWithImages(_ images : Array<UIImage>, timeInterval : TimeInterval = 0) {
    SJProgressHUD.showWaitWithImages(images, timeInterval: timeInterval)
}
/**
 显示菊花

 - parameter text:       需要显示的文字,如果不设置文字,则只显示菊花
 - parameter autoRemove: 是否自动移除,默认3秒后自动移除
 */
static func showWaiting(_ text: String = "", autoRemove: Bool = true) {
    SJProgressHUD.showWaitingWithText(text, autoRemove: autoRemove)
}
/**
 状态栏显示

 - parameter text:       需要显示的文字
 - parameter color:      背景颜色
 - parameter autoRemove: 是否自动移除,默认3秒后自动移除
 */
static func showStatusBarWithText(_ text: String = "OK", color: UIColor = UIColor(red: 131 / 255.0, green: 178 / 255.0, blue: 158 / 255.0, alpha: 1), autoRemove: Bool = true) {
    SJProgressHUD.showStatusBar(text, color: color, autoRemove: autoRemove)
}
/**
 只显示文字

 - parameter text:       需要显示的文字
 - parameter autoRemove: 是否自动移除,默认3秒后自动移除
 */
static func showOnlyText(_ text: String, autoRemove: Bool = true) {
    SJProgressHUD.onlyText(text, autoRemove: autoRemove)
}
/**
 Success样式

 - parameter successText: 需要显示的文字,默认为 Success!
 - parameter autoRemove:  是否自动移除,默认3秒后自动移除
 */
static func showSuccess(_ successText: String = "Success!", autoRemove: Bool = true) {
    SJProgressHUD.showText(.success, text: successText, autoRemove: autoRemove)
}
/**
 Error样式

 - parameter errorText:  需要显示的文字,默认为 Error!
 - parameter autoRemove: 是否自动移除,默认3秒后自动移除
 */
static func showError(_ errorText: String = "Error!", autoRemove: Bool = true) {
    SJProgressHUD.showText(.error, text: errorText, autoRemove: autoRemove)
}
/**
 Info样式

 - parameter infoText:   需要显示的文字,默认为 Info!
 - parameter autoRemove: 是否自动移除,默认3秒后自动移除
 */
static func showInfo(_ infoText: String = "info!", autoRemove: Bool = true) {
    SJProgressHUD.showText(.info, text: infoText, autoRemove: autoRemove)
}
/**
 移除HUD,会移除所有
 */
static func dismiss() {
    SJProgressHUD.clear()
}

}

private let circleSize = CGSize(width: 40, height: 40) private let windowBgColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.002) private func bgColor(_ alpha: CGFloat) -> UIColor { return UIColor(red: 0, green: 0, blue: 0, alpha: alpha) }

class SJProgressHUD : NSObject {

static var windows = Array<UIWindow!>()
static var angle: Double {
        return [0, 0, 180, 270, 90][UIApplication.shared.statusBarOrientation.hashValue] as Double
}
static fileprivate func showWaitWithImages(_ images : Array<UIImage>, timeInterval : TimeInterval) {

    let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    let imageView = UIImageView()
    imageView.frame = frame
    imageView.contentMode = .scaleAspectFit
    imageView.backgroundColor = bgColor(0.7)
    imageView.layer.cornerRadius = 10
    imageView.animationImages = images
    imageView.animationDuration = timeInterval == 0 ? TimeInterval(images.count) * 0.07 : timeInterval
    imageView.animationRepeatCount = 0
    imageView.startAnimating()

    _ = SJProgressHUD.createWindow(frame, view: imageView)

}
static fileprivate func showWaitingWithText(_ text: String, autoRemove: Bool) {

    let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    view.backgroundColor = bgColor(0.7)
    view.layer.cornerRadius = 10

    let activity = UIActivityIndicatorView(activityIndicatorStyle: .white)
    if !text.isEmpty {
        activity.frame = CGRect(x: 35, y: 25, width: 30, height: 30)

        let lable = SJProgressHUD.createLable()
        lable.frame = CGRect(x: 0, y: 55, width: 100, height: 45)
        lable.text = text
        view.addSubview(lable)
    }
    activity.frame = CGRect(x: 30, y: 30, width: 40, height: 40)
    activity.startAnimating()
    view.addSubview(activity)

    let window = SJProgressHUD.createWindow(view.frame, view: view)

    if autoRemove {
        perform(#selector(SJProgressHUD.removeHUD(_:)), with: window, afterDelay: 3)
    }

}
static fileprivate func showStatusBar(_ text: String, color: UIColor, autoRemove: Bool) {

    let frame = UIApplication.shared.statusBarFrame

    let lable = SJProgressHUD.createLable()
    lable.text = text

    let window = SJProgressHUD.createWindow(frame, view: lable)
    window.backgroundColor = color
    window.windowLevel = UIWindowLevelStatusBar
    lable.frame = frame
    window.center = CGPoint(x: frame.width * 0.5, y: frame.height * 0.5)
    lable.center = window.center

    if autoRemove {
        perform(#selector(SJProgressHUD.removeHUD(_:)), with: window, afterDelay: 3)
    }
}
static fileprivate func onlyText(_ text: String, autoRemove: Bool) {

    let view = UIView()
    view.backgroundColor = bgColor(0.7)
    view.layer.cornerRadius = 10

    let label = SJProgressHUD.createLable()
    label.text = text
    label.font = UIFont.systemFont(ofSize: 12)
    label.frame = CGRect(x: 5, y: 0, width: 140, height: 60)
    view.addSubview(label)

    let frame = CGRect(x: 0, y: 0, width: 150 , height: 60)
    view.frame = frame
    label.center = view.center

    let window = SJProgressHUD.createWindow(frame, view: view)

    if autoRemove {
        perform(#selector(SJProgressHUD.removeHUD(_:)), with: window, afterDelay: 2)
    }
}
static fileprivate func showText(_ type: ShowType, text: String, autoRemove: Bool) {
    let frame = CGRect(x: 0, y: 0, width: 100, height: 100)

    let view = UIView(frame: frame)
    view.layer.cornerRadius = 10
    view.backgroundColor = bgColor(0.7)

    var image = UIImage()
    switch type {
    case .success:
        image = drawImage.imageOfSuccess
    case .error:
        image = drawImage.imageOfError
    case .info:
        image = drawImage.imageOfInfo
    }

    let imageView = UIImageView(image: image)
    imageView.frame = CGRect(origin: CGPoint(x: 30, y: 25), size: circleSize)
    imageView.contentMode = .scaleAspectFit
    view.addSubview(imageView)

    let lable = SJProgressHUD.createLable()
    lable.frame = CGRect(x: 0, y: 70, width: 100, height: 30)
    lable.text = text
    view.addSubview(lable)

    let window = SJProgressHUD.createWindow(frame, view: view)

    if autoRemove {
        perform(#selector(SJProgressHUD.removeHUD(_:)), with: window, afterDelay: 3)
    }

}

static fileprivate func createLable() -> UILabel {
    let lable = UILabel()
    lable.font = UIFont.systemFont(ofSize: 10)
    lable.backgroundColor = UIColor.clear
    lable.textColor = UIColor.white
    lable.numberOfLines = 0
    lable.textAlignment = .center
    return lable
}
static fileprivate func createWindow(_ frame: CGRect, view: UIView) -> UIWindow {

    let window = UIWindow(frame: frame)
    window.backgroundColor = windowBgColor
    window.windowLevel = UIWindowLevelAlert

// window.transform = CGAffineTransform(rotationAngle: CGFloat(angle M_PI / 180)) window.transform = CGAffineTransform(rotationAngle: CGFloat(angle .pi / 180)) window.isHidden = false window.center = getCenter() window.addSubview(view) windows.append(window) return window } @objc static fileprivate func removeHUD(_ object: AnyObject) { if let window = object as? UIWindow { if let index = windows.index(where: { (item) -> Bool in return item == window }) { windows.remove(at: index) } } } static fileprivate func clear() { if windows.isEmpty { return } self.cancelPreviousPerformRequests(withTarget: self) windows.removeAll(keepingCapacity: false) } static func getCenter() -> CGPoint { let view = UIApplication.shared.keyWindow?.subviews.first as UIView! if UIApplication.shared.statusBarOrientation.hashValue >= 3 { return CGPoint(x: view!.center.y, y: view!.center.x) } else { return view!.center } } }

class drawImage {

struct imageCache {
    static var imageOfSuccess: UIImage?
    static var imageOfError: UIImage?
    static var imageOfInfo: UIImage?
}

class func draw(_ type : ShowType) {

    let path = UIBezierPath()

    path.move(to: CGPoint(x: 40, y: 20))

// path.addArc(withCenter: CGPoint(x: 20, y: 20), radius: 19, startAngle: 0, endAngle: CGFloat(M_PI2), clockwise: true) path.addArc(withCenter: CGPoint(x: 20, y: 20), radius: 19, startAngle: 0, endAngle: CGFloat( Double.pi 2 ), clockwise: true)

    path.close()

    switch type {
    case .success:
        path.move(to: CGPoint(x: 15, y: 20))
        path.addLine(to: CGPoint(x: 20, y: 25))
        path.addLine(to: CGPoint(x: 30, y: 15))
        path.move(to: CGPoint(x: 15, y: 20))
        path.close()
    case .error:
        path.move(to: CGPoint(x: 10, y: 10))
        path.addLine(to: CGPoint(x: 30, y: 30))
        path.move(to: CGPoint(x: 10, y: 30))
        path.addLine(to: CGPoint(x: 30, y: 10))
        path.move(to: CGPoint(x: 10, y: 10))
        path.close()
    case .info:
        path.move(to: CGPoint(x: 20, y: 8))
        path.addLine(to: CGPoint(x: 20, y: 28))
        path.move(to: CGPoint(x: 20, y: 8))
        path.close()

        let tmpPath = UIBezierPath()
        tmpPath.move(to: CGPoint(x: 20, y: 30))

// tmpPath.addArc(withCenter: CGPoint(x: 20, y: 30), radius: 1, startAngle: 0, endAngle: CGFloat(M_PI2), clockwise: true) tmpPath.addArc(withCenter: CGPoint(x: 20, y: 30), radius: 1, startAngle: 0, endAngle: CGFloat( Double.pi2), clockwise: true)

        tmpPath.close()
        UIColor.white.setFill()
        tmpPath.fill()
    }
    UIColor.white.setStroke()
    path.stroke()
}

class var imageOfSuccess: UIImage {
    if imageCache.imageOfSuccess != nil {
        return imageCache.imageOfSuccess!
    }
    UIGraphicsBeginImageContextWithOptions(circleSize, false, 0)
    drawImage.draw(.success)
    imageCache.imageOfSuccess = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return imageCache.imageOfSuccess!
}
class var imageOfError: UIImage {
    if imageCache.imageOfError != nil {
        return imageCache.imageOfError!
    }
    UIGraphicsBeginImageContextWithOptions(circleSize, false, 0)
    drawImage.draw(.error)
    imageCache.imageOfError = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return imageCache.imageOfError!
}
class var imageOfInfo: UIImage {
    if imageCache.imageOfInfo != nil {
        return imageCache.imageOfInfo!
    }
    UIGraphicsBeginImageContextWithOptions(circleSize, false, 0)
    drawImage.draw(.info)
    imageCache.imageOfInfo = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return imageCache.imageOfInfo!
}

}

ferassaqqa commented 7 years ago

Just update the files with the new Code ? or there other things we have to do ?

De-Lac commented 7 years ago

I copied modification of @wolfmanwoking , plus I've added the package.json to be Cordova 7 compliant (to fix the plugin add error) in my fork: https://github.com/De-Lac/phonertc

It's not perfect, but I think I'm on the right way. The project doesn't compile with ionic cordova build ios, but it successfully compiles in Xcode.

I am currently using it with success.

$ ionic info

cli packages: (/usr/local/lib/node_modules)

    @ionic/cli-utils  : 1.9.2
    ionic (Ionic CLI) : 3.9.2

global packages:

    Cordova CLI : 7.0.1 
    Gulp CLI    : CLI version 3.9.1 Local version 3.9.1

local packages:

    Cordova Platforms : browser 4.1.0 ios 4.4.0
    Ionic Framework   : ionic1 1.3.3

System:

    ios-deploy : 1.9.1 
    ios-sim    : 5.0.8 
    Node       : v8.1.3
    npm        : 5.3.0 
    OS         : macOS Sierra
    Xcode      : Xcode 8.3.3 Build version 8E3004b 
MrShakes commented 7 years ago

@De-Lac @wolfmanwoking Getting Use of unresolved identifier 'SJProgressHUD' in the PhoneRTCPlugin.swift file and yes SJProgressHUD.swift is in the src/ios folder of the plugin file.

Have at least 5 errors when running ionic cordova build ios:

[ERROR] An error occurred while running cordova build ios (exit code 1):

        (truncated) ...  SecureChat -emit-module-path 
        /Users/shakes/Library/Developer/Xcode/DerivedData/SecureChat-awsdbeqrzpuuapgkwxmvennmqvlo/Build/Intermediates/SecureChat.build/Debug-iphonesimulator/SecureChat.build/Objects-normal/x86_64/SessionDescriptionDelegate~partial.swiftmodule 
        -emit-dependencies-path 
        /Users/shakes/Library/Developer/Xcode/DerivedData/SecureChat-awsdbeqrzpuuapgkwxmvennmqvlo/Build/Intermediates/SecureChat.build/Debug-iphonesimulator/SecureChat.build/Objects-normal/x86_64/SessionDescriptionDelegate.d 
        -emit-reference-dependencies-path 
        /Users/shakes/Library/Developer/Xcode/DerivedData/SecureChat-awsdbeqrzpuuapgkwxmvennmqvlo/Build/Intermediates/SecureChat.build/Debug-iphonesimulator/SecureChat.build/Objects-normal/x86_64/SessionDescriptionDelegate.swiftdeps 
        -o 
        /Users/shakes/Library/Developer/Xcode/DerivedData/SecureChat-awsdbeqrzpuuapgkwxmvennmqvlo/Build/Intermediates/SecureChat.build/Debug-iphonesimulator/SecureChat.build/Objects-normal/x86_64/SessionDescriptionDelegate.o
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/SessionDescriptionDelegate.swift:3:49: 
        error: use of undeclared type 'RTCSessionDescriptionDelegate'
        class SessionDescriptionDelegate : UIResponder, 
        RTCSessionDescriptionDelegate {

        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/SessionDescriptionDelegate.swift:10:39: 
        error: use of undeclared type 'RTCPeerConnection'
        func peerConnection(_ peerConnection: RTCPeerConnection!,
                                               ^~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/SessionDescriptionDelegate.swift:11:46: 
        error: use of undeclared type 'RTCSessionDescription'
             didCreateSessionDescription originalSdp: RTCSessionDescription!, 
        error: Error!) {
                                                      ^~~~~~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/SessionDescriptionDelegate.swift:47:39: 
        error: use of undeclared type 'RTCPeerConnection'
        func peerConnection(_ peerConnection: RTCPeerConnection!,
                                               ^~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/PCObserver.swift:3:30: 
        error: use of undeclared type 'RTCPeerConnectionDelegate'
        class PCObserver : NSObject, RTCPeerConnectionDelegate {
                                      ^~~~~~~~~~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/SessionDescriptionDelegate.swift:17:15: 
        error: use of unresolved identifier 'RTCSessionDescription'
             let sdp = RTCSessionDescription(
                       ^~~~~~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/Session.swift:7:21: 
        error: use of undeclared type 'RTCPeerConnection'
        var peerConnection: RTCPeerConnection!
                             ^~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/Session.swift:6:18: 
        error: use of undeclared type 'RTCMediaConstraints'
        var constraints: RTCMediaConstraints
                          ^~~~~~~~~~~~~~~~~~~
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/Session.swift:9:30: 
        error: use of undeclared type 'RTCICECandidate'
        var queuedRemoteCandidates: [RTCICECandidate]?
                                      ^~~~~~~~~~~~~~~

        ** BUILD FAILED **

        The following build commands failed:
            CompileSwift normal x86_64 
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/PhoneRTCPlugin.swift
            CompileSwift normal x86_64 
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/PCObserver.swift
            CompileSwift normal x86_64 
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/Session.swift
            CompileSwift normal x86_64 
        /Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/SecureChat/Plugins/com.dooble.phonertc/SessionDescriptionDelegate.swift
            CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
        (5 failures)
        Error: Error code 65 for command: xcodebuild with args: 
        -xcconfig,/Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/cordova/build-debug.xcconfig,-workspace,SecureChat.xcworkspace,-scheme,SecureChat,-configuration,Debug,-sdk,iphonesimulator,-destination,platform=iOS 
        Simulator,name=iPhone 
        SE,build,CONFIGURATION_BUILD_DIR=/Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/build/emulator,SHARED_PRECOMPS_DIR=/Users/shakes/Desktop/Awork/s-c/securechat/platforms/ios/build/sharedpch

System info:

cli packages: (/usr/local/lib/node_modules)

    @ionic/cli-utils  : 1.9.2
    ionic (Ionic CLI) : 3.9.2

global packages:

    Cordova CLI : 7.0.1 

local packages:

    Cordova Platforms : android 6.2.3 ios 4.4.0
    Ionic Framework   : ionic1 1.3.2

System:

    ios-deploy : 1.9.1 
    ios-sim    : 5.0.8 
    Node       : v6.10.2
    npm        : 3.10.10 
    OS         : macOS Sierra
    Xcode      : Xcode 8.3.3 Build version 8E3004b
De-Lac commented 7 years ago

add that file manually in the Xcode project. Open Xcode, open the project, find where are the other files .swift, right-click, add-file and manually paste the code. I don't know why it's missing during the installation

MrShakes commented 7 years ago

And a few more errors: screen shot 2017-09-13 at 10 31 31