uniapp 跟app 的流程图
以下是具体app实现videoCall的具体
wss的open、之后create session,然后在create handlerid 这个流程基本上可以说是所有的janus的插件必备的流程
进行register信令注册,这个注册无论是主叫还是被叫都是需要的
["handle_id": 3508556780812699, "body": ["pin": 000, "request": register, "muted": 1, "username": "ios999", "display": 12, "room": 599224, "token": 12], "token": "xxxxxxxxxx", "session_id": 3189233037691542, "janus": "message", "transaction": "i12-register-461789"]
注册成功会收到一个event
["plugindata": { data = { result = { event = registered; username = ios999; }; videocall = event; }; plugin = "janus.plugin.videocall"; }, "session_id": 3189233037691542, "sender": 3508556780812699, "transaction": i12-register-461789, "janus": event]
此时就可以进行主叫了,但是需要一些对peer的前置处理
public var rtcFactory:RTCPeerConnectionFactory = { let oo = RTCPeerConnectionFactoryOptions() // RTCPeerConnectionFactory(encoderFactory: nil, decoderFactory: nil, audioDevice: (any RTCAudioDevice)?) // RTCPeerConnectionFactory(encoderFactory: nil, decoderFactory: nil, audioDevice: CustomAudioDevice()) // 有用到video的话,解码跟编码不能为空 let encoderFactory = RTCDefaultVideoEncoderFactory() let decoderFactory = RTCDefaultVideoDecoderFactory() let aaa = RTCPeerConnectionFactory(encoderFactory: encoderFactory, decoderFactory: decoderFactory) return aaa }()
/// 对音频处理 func initAudio(){ let audioSource = rtcFactory.audioSource(with: RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)) localAudioTrack = rtcFactory.audioTrack(with: audioSource, trackId: kARDAudioTrackId) let rtpSender = peer?.sender(withKind: kRTCMediaStreamTrackKindAudio, streamId: kARDAudioTrackId) rtpSender?.track = localAudioTrack } /// 对视频处理 override func initVideo() { let videoSource = rtcFactory.videoSource() capturer = RTCCameraVideoCapturer(delegate: videoSource) localVideoTrack = rtcFactory.videoTrack(with: videoSource, trackId: kARDVideoTrackId) let rtpVideoSender = peer?.sender(withKind: kRTCMediaStreamTrackKindVideo, streamId: kARDVideoTrackId) rtpVideoSender?.track = localVideoTrack } /// 开启摄像头 func startCapture(){ guard let capturer = capturer else { return } guard let device = RTCCameraVideoCapturer.captureDevices().first(where: { $0.position == .front }) ?? RTCCameraVideoCapturer.captureDevices().first else { return } let formats = RTCCameraVideoCapturer.supportedFormats(for: device) let targetFormat = formats.sorted { f1, f2 in CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width < CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width }.last let fps = (targetFormat?.videoSupportedFrameRateRanges.first?.maxFrameRate ?? 30) capturer.startCapture(with: device, format: targetFormat!, fps: Int(fps)) { err in if let err = err { print("摄像头启动失败: \(err)") } } }
对audio跟video初始化后,在开启摄像头,然后就可以peer.offer,此时是属于主叫,所以要 peer?.setLocalDescription
override func doOffer() { let aa:[String:String] = ["OfferToReceiveAudio":"true","OfferToReceiveVideo":"true"] let mediaConstraints = RTCMediaConstraints.init(mandatoryConstraints: aa, optionalConstraints: nil) peer?.offer(for: mediaConstraints, completionHandler: { [weak self] sdp, err in guard let this = self else { return } if err != nil { print("peer.offer出问题 \(err!)") }else{ print("peer.offer正常 (sdp!)") // 主叫跟被叫的流程是不同的 if BasePlugin.video().isCall{ this.setLocal(sdp!) }else{ this.localSdp = sdp this.setLocalCb(err) } } }) }
setLocal成功了,就可以发送call信令
sigState = .call let key = sigState.rawValue let jsep:Json = ["type":"offer","sdp":objPeer!.localSdp!.sdp] let msg:Json = ["request":key,"username":calledName!] var pp:[String:Any] = ["janus":"message","body":msg,"jsep":jsep,"transaction":key] objWss.send(msg: &pp)
A 发送call信令后,此时B会收到incoming的时间,这个时候B要去发送accept信令
{ "videocall" : "event", "result" : { "event" : "incomingcall", "username" : "<your username>" } }
发送accept信令,注意,是B去发送 sigState = .call let key = sigState.rawValue let jsep:Json = ["type":"offer","sdp":objPeer!.localSdp!.sdp] let msg:Json = ["request":key,"username":calledName!] var pp:[String:Any] = ["janus":"message","body":msg,"jsep":jsep,"transaction":key] objWss.send(msg: &pp)
之后A收到一个accepted的event,然后执行
@objc func setRemote(_ sdp: RTCSessionDescription) { self.peer?.setRemoteDescription(sdp, completionHandler: { err in print("peer.setRemote出问题 \(String(describing: err))") self.remoteSdp = sdp self.setRemoteCb(err) }) } override func setRemoteCb(_ err: (any Error)?) { for vvv in remoteCands { peer?.add(vvv) { error in let msg = error == nil ? "nil" : error?.localizedDescription print("\\(vvv)\n 添加对端ICE--error \(msg!)") } } if !BasePlugin.video().isCall { doAnswer(remoteSdp!) } }
IPeerObj父类
open class IPeerObj:NSObject{ let OPTION = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: ["DtlsSrtpKeyAgreement":"true"]) var errState:RoomError = .none var peer:RTCPeerConnection? var localSdp:RTCSessionDescription? var remoteSdp:RTCSessionDescription? var localAudioTrack:RTCAudioTrack? var remoteAudioTrack:RTCAudioTrack? var remoteCands:[RTCIceCandidate] = [] var subPeerDelegate:RTCPeerConnectionDelegate? init(peer: RTCPeerConnection? = nil, localSdp: RTCSessionDescription? = nil, remoteSdp: RTCSessionDescription? = nil, localAudioTrack: RTCAudioTrack? = nil, remoteAudioTrack: RTCAudioTrack? = nil, remoteCands: [RTCIceCandidate] = [], subPeerDelegate: RTCPeerConnectionDelegate? = nil) { self.peer = peer self.localSdp = localSdp self.remoteSdp = remoteSdp self.localAudioTrack = localAudioTrack self.remoteAudioTrack = remoteAudioTrack self.remoteCands = remoteCands self.subPeerDelegate = subPeerDelegate } func initPeer(conObj:ConnectObj){ let config = IPeerObj.newRtcConfig(conObj) self.peer = rtcFactory.peerConnection(with: config, constraints: OPTION, delegate: self)! print("initpeer \(String(describing: self.peer))") initAudio() initVideo() } private static func newRtcConfig(_ obj:ConnectObj)->RTCConfiguration { let config = RTCConfiguration() config.sdpSemantics = .planB // config.iceTransportPolicy = .relay config.offerExtmapAllowMixed = true config.tcpCandidatePolicy = .disabled config.maxIPv6Networks = 0 config.iceCandidatePoolSize = 0 config.iceTransportPolicy = .all let ice1 = RTCIceServer(urlStrings: [obj.stunUrl]) let ice2 = RTCIceServer(urlStrings: [obj.turnUrl], username: obj.turnUsername, credential: obj.turnPassword) config.iceServers = [ice2,ice1] //无论这个是那种,都会自动发candidate过去 config.continualGatheringPolicy = .gatherOnce return config } open func initVideo(){ } /// 对音频处理 func initAudio(){ let audioSource = rtcFactory.audioSource(with: RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)) localAudioTrack = rtcFactory.audioTrack(with: audioSource, trackId: kARDAudioTrackId) let rtpSender = peer?.sender(withKind: kRTCMediaStreamTrackKindAudio, streamId: kARDAudioTrackId) rtpSender?.track = localAudioTrack } func resetAll(){ errState = .none //将所有的都重置,就是不能peer.close() remoteAudioTrack?.isEnabled = false localAudioTrack?.isEnabled = false remoteAudioTrack = nil localAudioTrack = nil peer?.delegate = nil peer?.senders.forEach({ send in send.track?.isEnabled = false peer?.removeTrack(send) }) peer?.remove(remoteCands) } func resetPeer(){ } } extension IPeerObj:OfferAnser { @objc func setLocalCb(_ err: Error?) { } @objc func setRemoteCb(_ err: Error?) { // if err != nil { return } // for vvv in remoteCands { // peer?.add(vvv) { error in // let msg = err == nil ? "nil" : err?.localizedDescription // print("\\(vvv)\n 添加对端ICEd \(msg!)") // } // } } @objc func doOffer() { } @objc func doAnswer(_ jsep: RTCSessionDescription) { } @objc func setLocal(_ sdp: RTCSessionDescription) { peer?.setLocalDescription(sdp) { err in print("peer.setLocal出问题 \(String(describing: err))") self.localSdp = sdp self.setLocalCb(err) } } @objc func setRemote(_ sdp: RTCSessionDescription) { self.peer?.setRemoteDescription(sdp, completionHandler: { err in print("peer.setRemote出问题 \(String(describing: err))") self.remoteSdp = sdp self.setRemoteCb(err) }) } } extension IPeerObj:RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { // cbAudioImp?.peerConnection(peerConnection, didChange: stateChanged) subPeerDelegate?.peerConnection(peerConnection, didChange: stateChanged) } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { // cbAudioImp?.peerConnection(peerConnection, didAdd: stream) subPeerDelegate?.peerConnection(peerConnection, didAdd: stream) if let audioTrack = stream.audioTracks.first { remoteAudioTrack = audioTrack // 保存远程音频轨道 } } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd rtpReceiver: RTCRtpReceiver, streams mediaStreams: [RTCMediaStream]) { subPeerDelegate?.peerConnection?(peerConnection, didAdd: rtpReceiver, streams: mediaStreams) } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { // cbAudioImp?.peerConnection(peerConnection, didRemove: stream) subPeerDelegate?.peerConnection(peerConnection, didRemove: stream) } public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { print("peerConnectionShouldNegotiate") // cbAudioImp?.peerConnectionShouldNegotiate(peerConnection) subPeerDelegate?.peerConnectionShouldNegotiate(peerConnection) // doPeerOffer() } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { let objs = ["NEW","CHECKING", "CONNECTED", "COMPLETED", "FAILED", "DISCONNECTED", "CLOSED","COUNT"] print("cb ==== didChange RTCIceConnectionState \(objs[newState.rawValue])") // cbAudioImp?.peerConnection(peerConnection, didChange: newState) subPeerDelegate?.peerConnection(peerConnection, didChange: newState) } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { let str = ["RTCIceGatheringStateNew","RTCIceGatheringStateGathering","RTCIceGatheringStateComplete",] print("RTCIceGatheringState = \(str[newState.rawValue])") subPeerDelegate?.peerConnection(peerConnection, didChange: newState) // cbAudioImp?.peerConnection(peerConnection, didChange: newState) if newState == .complete { // objPeerImp?.signalTrickleCompled() } } public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { subPeerDelegate?.peerConnection(peerConnection, didGenerate: candidate) // cbAudioImp?.peerConnection(peerConnection, didGenerate: candidate) // objPeerImp?.signalTrickle(candidate: candidate) } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { subPeerDelegate?.peerConnection(peerConnection, didRemove: candidates) // cbAudioImp?.peerConnection(peerConnection, didRemove: candidates) } public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { subPeerDelegate?.peerConnection(peerConnection, didOpen: dataChannel) // cbAudioImp?.peerConnection(peerConnection, didOpen: dataChannel) } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCPeerConnectionState) { let objs = ["RTCPeerConnectionStateNew", "RTCPeerConnectionStateConnecting", "RTCPeerConnectionStateConnected", "RTCPeerConnectionStateDisconnected", "RTCPeerConnectionStateFailed", "RTCPeerConnectionStateClosed"] print("-------RTCPeerConnectionState--> \(objs[newState.rawValue])") if newState == .failed && networkStatus == .notReachable { //有些时候peer fail可能不是网络原因 WssObject.share().state = .disconnect } subPeerDelegate?.peerConnection?(peerConnection, didChange: newState) // objPeerImp?.peerStateChange(state: newState) } }
VideoCallPeerObj 子类
class VideoCallPeerObj:IPeerObj { var capturer: RTCCameraVideoCapturer? var localVideoTrack: RTCVideoTrack? var remoteVideoTrack: RTCVideoTrack? public var delegateUI:CallViewVcStatusDelegate? /// 开启摄像头 func startCapture(){ guard let capturer = capturer else { return } guard let device = RTCCameraVideoCapturer.captureDevices().first(where: { $0.position == .front }) ?? RTCCameraVideoCapturer.captureDevices().first else { return } let formats = RTCCameraVideoCapturer.supportedFormats(for: device) let targetFormat = formats.sorted { f1, f2 in CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width < CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width }.last let fps = (targetFormat?.videoSupportedFrameRateRanges.first?.maxFrameRate ?? 30) capturer.startCapture(with: device, format: targetFormat!, fps: Int(fps)) { err in if let err = err { print("摄像头启动失败: \(err)") } } } /// 对视频处理 override func initVideo() { let videoSource = rtcFactory.videoSource() capturer = RTCCameraVideoCapturer(delegate: videoSource) localVideoTrack = rtcFactory.videoTrack(with: videoSource, trackId: kARDVideoTrackId) let rtpVideoSender = peer?.sender(withKind: kRTCMediaStreamTrackKindVideo, streamId: kARDVideoTrackId) rtpVideoSender?.track = localVideoTrack } override func setRemoteCb(_ err: (any Error)?) { for vvv in remoteCands { peer?.add(vvv) { error in let msg = error == nil ? "nil" : error?.localizedDescription print("\\(vvv)\n 添加对端ICE--error \(msg!)") } } if !BasePlugin.video().isCall { doAnswer(remoteSdp!) } } override func doAnswer(_ sdp: RTCSessionDescription) { let aa:[String:String] = ["OfferToReceiveAudio":"true","OfferToReceiveVideo":"true"] let mediaConstraints = RTCMediaConstraints.init(mandatoryConstraints: aa, optionalConstraints: nil) peer?.answer(for: mediaConstraints, completionHandler: { s, err in self.localSdp = s self.setLocal(self.localSdp!) }) } override func doOffer() { let aa:[String:String] = ["OfferToReceiveAudio":"true","OfferToReceiveVideo":"true"] let mediaConstraints = RTCMediaConstraints.init(mandatoryConstraints: aa, optionalConstraints: nil) peer?.offer(for: mediaConstraints, completionHandler: { [weak self] sdp, err in guard let this = self else { return } if err != nil { print("peer.offer出问题 \(err!)") }else{ print("peer.offer正常 (sdp!)") // 主叫跟被叫的流程是不同的 if BasePlugin.video().isCall{ this.setLocal(sdp!) }else{ this.localSdp = sdp this.setLocalCb(err) } } }) } override func setLocalCb(_ err: (any Error)?) { let aaa = err == nil ? (true,"peer.setLocal正常") : (false,"peer.setLocal出问题 \(err!)") if aaa.0 { let videoP = BasePlugin.video() if videoP.isCall { if videoP.isSet { videoP.set() }else{ videoP.call() } }else{ videoP.accpet() } } } }
BasePlugin父类
open class BasePlugin:NSObject{ lazy var objWss = WssObject.share() var objPeer:IPeerObj? var sigState:SignalState = .none var remoteCands:[RTCIceCandidate] = [] var pluginTag:String { return "" } private static var objBase:BasePlugin! public static func reSetAllObj(){ WssObject.share().resetAll() if objBase != nil { objBase.objPeer?.peer?.delegate = nil objBase.objPeer = nil objBase = nil } } public static func shared()->BasePlugin { return objBase } public static func audio()->AudioPlugin { if objBase == nil { objBase = factory(0) } return objBase as! AudioPlugin } public static func video()->VideoCallPlugin { if objBase == nil { objBase = factory(1) }else{ } return objBase as! VideoCallPlugin } public static func factory(_ plugin:Int)->BasePlugin { if plugin == 0 { objBase = AudioPlugin() }else if plugin == 1 { objBase = VideoCallPlugin() } return objBase } func saveRemoteCand(sdp:String,_ sdpMLineIndex:Int32,_ sdpMid:String){ let split = sdp.split(separator: " ") if split.count > 2 { // 这里实际要考虑video的情况 不知道为啥是0 let iceCan = RTCIceCandidate(sdp: sdp, sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) remoteCands.append(iceCan) objPeer?.remoteCands.append(iceCan) }else{ print("saveRemoteCand sdp -> \(sdp) 有问题") } } func createSid() { sigState = .create let key = SignalState.create.rawValue let dateFormatter = DateFormatter() dateFormatter.timeZone = TimeZone.current dateFormatter.dateFormat = "mmss" var iii = Int64("3\(personObj.phoneNum)\(dateFormatter.string(from: Date()))")! if objWss.conObj.sessionId != nil { iii = objWss.conObj.sessionId! } var pp:Json = ["janus":key,"transaction":key,"id":iii] objWss.send(msg: &pp) } func createHid(sid:Int64?) { objWss.conObj.sessionId = sid sigState = .attach let key = sigState.rawValue var pp:Json = ["transaction":key, "janus":key,"plugin":pluginTag] // conObj.handerId = nil objWss.send(msg: &pp) } func claim() { sigState = .claim var pp: [String : Any] = ["janus":sigState.rawValue,"transaction":SignalState.claim.rawValue] objWss.send(msg: &pp) } func sendCompledTrick() { var dict:Json = ["janus":"trickle","candidate":["completed":1],"transaction":"trickcompled"] objWss.send(msg: &dict) } func sendTrick(_ dict:Json){ var dict:Json = ["janus":"trickle","candidate":dict,"transaction":"trick"] objWss.send(msg: &dict) } // MARK: 具体需要子类去实现 /// 创建handleid成功 open func createHidSuccess(){ } /// claim换绑成功 open func claimSuccess(){ } open func waitNewPeer(err:RoomError,_ reason:String = "",_ event:Json = [:]){ } open func connectStateCb(type:ConnectStateType, _ add :[String:Any] = [:]){} /// 收到hangup时间 open func receiveHangUp(_ event:Json){ print("hangup = \(event["reason"] as! String)") } open func receiveMedia(_ event:Json){ print("receiveMedia") } open func receiveEvent(_ event:Json){ var p_data:Json! var data:Json! if let plugindata = event["plugindata"] as? Json, let obj = plugindata["data"] as? Json{ p_data = plugindata data = obj } if p_data == nil { return } let audiobridge = data["audiobridge"] as? String let code = data["error_code"] as? Int let reason = data["error"] as? String if let a = code,let b = reason { //丢给子类去处理以及处理公共的 errorEvent(a, b, event: event) } } open func receiveWebrtcUpEvent(_ event:Json){} /// 这个才是外层的error 只处理公共的 open func receiveErrorEvent(_ event:Json){ guard let err = event["error"] as? Json else { print("收到janus的错误,转换出问题") return } let code = RoomError(rawValue: err["code"] as! Int) ?? .none let reason = err["reason"] as! String if code == .unHandleRequest { waitNewPeer(err: .unHandleRequest,reason,event) }else if code == .noSession { waitNewPeer(err: .noSession,reason,event) }else if code == .localIceErr { waitNewPeer(err: .localIceErr,reason,event) }else if code == .sessionUse { claim() } } /// 这个是event的错误,并不是最外层wss的Error的错误 open func errorEvent(_ code:Int,_ reason:String, event:Json = [:]){ let errState = RoomError(rawValue: code) ?? .none if errState == RoomError.unknownRequest { print("RoomError.unknownRequest",level: .error) } } } extension BasePlugin:RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCPeerConnectionState) { print("cb ==== didChange RTCPeerConnectionState \(newState)") } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd rtpReceiver: RTCRtpReceiver, streams mediaStreams: [RTCMediaStream]) { } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { } public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { objPeer?.doOffer() } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { let objs = ["NEW","CHECKING", "CONNECTED", "COMPLETED", "FAILED", "DISCONNECTED", "CLOSED","COUNT"] print("cb ==== didChange RTCIceConnectionState \(objs[newState.rawValue])") } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { if newState == .complete { sendCompledTrick() } } public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { let cand = ["candidate":candidate.sdp,"sdpMid":candidate.sdpMid!,"sdpMLineIndex":candidate.sdpMLineIndex] as Json sendTrick(cand) } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { } public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { } }
VideoCallPlugin 子类
public class VideoCallPlugin:BasePlugin { var connectJson:Json! var callId:String? var callName:String? var calledName:String? /// 默认是拨打 var isCall = true var isSet = false var delegateUI:CallViewVcStatusDelegate? var updateTextUI = PublishSubject<(CallViewVcStatus,String)>() var startCountDown = PublishSubject<Bool>() override var pluginTag: String { return "janus.plugin.videocall" } public func setDelegateUI(_ delegate:CallViewVcStatusDelegate?){ delegateUI = delegate } public override func createHidSuccess() { register(name: callName!) } public override func claimSuccess() { isSet = true objPeer?.peer?.restartIce() // objPeer?.doOffer() } func register(name:String) { print("发送register") sigState = .register let key = sigState.rawValue objWss.joinRoomBody.request = key var mmm = modelToDictionary(objWss.joinRoomBody)! mmm["username"] = name var pp: [String : Any] = ["body":mmm,"janus":"message","transaction":key] objWss.send(msg: &pp) } func actionCall(){ updateTextUI.onNext((.b_register,"对方注册成功准备呼叫")) let videoObj = VideoCallPeerObj(remoteCands: remoteCands, subPeerDelegate: self) objPeer = videoObj videoObj.initPeer(conObj: objWss.conObj) videoObj.startCapture() videoObj.doOffer() delegateUI?.status(didChangeLocalRender: videoObj.localVideoTrack) } func set() { print("发送set") sigState = .set let key = sigState.rawValue let jsep:Json = ["type":"offer","sdp":objPeer!.localSdp!.sdp] let msg:Json = ["request":key,"audio":true,"video":true] var pp:[String:Any] = ["janus":"message","body":msg,"jsep":jsep,"transaction":key] objWss.send(msg: &pp) updateTextUI.onNext((.none,"已set")) } func call() { print("发送call") sigState = .call let key = sigState.rawValue let jsep:Json = ["type":"offer","sdp":objPeer!.localSdp!.sdp] let msg:Json = ["request":key,"username":calledName!] var pp:[String:Any] = ["janus":"message","body":msg,"jsep":jsep,"transaction":key] objWss.send(msg: &pp) updateTextUI.onNext((.b_wait_accept, "已呼叫等待对方接听")) } func actionAccpet(){ objPeer?.setRemote(objPeer!.remoteSdp!) } func actionHangUp(){ hangup() } func accpet() { print("accepte") sigState = .accept let key = sigState.rawValue let jsep:Json = ["type": "answer", "sdp": objPeer!.localSdp!.sdp] let msg:Json = ["request":key,"username":calledName!] var pp:[String:Any] = ["janus":"message","body":msg,"jsep":jsep,"transaction":key] objWss.send(msg: &pp) } func hangup() { print("hangup") sigState = .hangup let key = sigState.rawValue let msg:Json = ["request":key] var pp:[String:Any] = ["janus":"message","body":msg,"transaction":key] objWss.send(msg: &pp) } @objc public func connect(json: [String : Any], cb: @escaping WXCallBack) { let config = json["config"] as! Json print("connect 收到connect json = \(dict2JsonStr(config))",level: .error) var obj = ConnectObj() obj.token = config["token"] as! String obj.url = config["wssUrl"] as! String obj.stunUrl = config["stunUrl"] as! String obj.turnUrl = config["turnUrl"] as! String obj.turnPassword = config["turnPassword"] as! String obj.turnUsername = config["turnUsername"] as! String obj.wssKey = config["wssKey"] as! String objWss.state = .new objWss.initSocket(obj: obj) objWss.noRepeatConnect() } public override func receiveMedia(_ event: Json) { updateTextUI.onNext((.none,"收到Media要开启计时")) startCountDown.onNext(true) } public override func receiveEvent(_ event: Json) { super.receiveEvent(event) let trans = event["transaction"] as? String let plugindata = event["plugindata"] as? Json if let trans = trans,trans.contains("register") { updateTextUI.onNext((.b_register,"等待对方注册")) /* //无论是拨打还是被拨打,都需要先注册,然后在new if objPeer?.peer?.connectionState == .disconnected { set() } */ let result = (plugindata!["data"] as! Json )["result"] as! Json VideoCallPluginConnectCb?(["type":"registered","id":callId!, "data":result,"msg":"注册成功"],true) }else if let trans = trans,trans.contains("call") { print("正在calling \(dict2JsonStr(event)),等到对方接听") }else if let pp = plugindata,let data = pp["data"] as? Json,let result = data["result"] as? Json, let type = result["event"] as? String { let username = result["username"] as? String if type == "accepted" { //对方接受的话有username if let user = username { print("对方 \(user) 接受了") updateTextUI.onNext((.b_accept ,"已经接听")) let jsep = event["jsep"] as! Json let sdp = RTCSessionDescription(type: .answer, sdp: jsep["sdp"] as! String) objPeer?.setRemote(sdp) VideoCallPluginConnectCb?(["type":"accepted","id":callId!, "data":result,"msg":"已接通"],true) }else { } // setremote成功后,估计需要candidate的操作 }else if type == "incomingcall",let user = username { print("对方 \(user) incomingcall") self.isCall = false self.calledName = user let videoObj = VideoCallPeerObj(remoteCands: remoteCands, subPeerDelegate: self) objPeer = videoObj videoObj.initPeer(conObj: objWss.conObj) videoObj.startCapture() delegateUI?.status(didChangeLocalRender: videoObj.localVideoTrack) let jsep = event["jsep"] as! Json let remoteType = jsep["type"] as! String let remoteSDP = jsep["sdp"] as! String let sdp = RTCSessionDescription(type:.offer, sdp: remoteSDP) objPeer?.remoteSdp = sdp VideoCallPluginConnectCb?(["type":"incomingcall","id":callId!, "data":result,"msg":"收到来电"],true) }else if type == "update" { let jsep = event["jsep"] as! Json let sdp = RTCSessionDescription(type: .answer, sdp: jsep["sdp"] as! String) objPeer?.setRemote(sdp) }else if type == "hangup" { delegateUI?.status(onHangup: nil) } }else{ print("没有处理的再次输出 event = \(dict2JsonStr(event))") } } public override func peerConnection(_ peerConnection: RTCPeerConnection, didAdd rtpReceiver: RTCRtpReceiver, streams mediaStreams: [RTCMediaStream]) { super.peerConnection(peerConnection, didAdd: rtpReceiver, streams: mediaStreams) if let track = rtpReceiver.track as? RTCVideoTrack { print(mediaStreams) let videoP = objPeer as! VideoCallPeerObj videoP.remoteVideoTrack = track delegateUI?.status(didChangeRemoteRender: track) } } public override func receiveHangUp(_ event: Json) { delegateUI?.status(onHangup: nil) } }