Мой код работает 1: 1, но не работает с 1: Многие у меня есть свой собственный внутренний сервер (swift) с WebSocket, а также мой пользователь iS I для поддержки WebRtc
Вот WebRtcМодель и VideoStreamViewController
import UIKit
class WebRTC: NSObject, RTCPeerConnectionDelegate, RTCEAGLVideoViewDelegate {
private var didReceiveRemoteStream:( () -> Void )?
private var onCreatedLocalSdp:((_ localSdp: NSDictionary) -> Void )?
private let factory = RTCPeerConnectionFactory()
private var localStream: RTCMediaStream?
private var localRenderView = RTCEAGLVideoView()
private let _localView = UIView(frame:CGRect(x:0, y:0, width: windowWidth(), height: windowHeight()))
private var remoteStream: RTCMediaStream?
private var remoteRenderView = RTCEAGLVideoView()
private let _remoteView = UIView(frame: CGRect(x: 0, y: 0, width: windowWidth(), height: windowWidth()))
private var peerConnections: [String: RTCPeerConnection] = [String: RTCPeerConnection]()
// private var peerConnections = [String: RTCPeerConnection]()
private var peerConnectionUserList = [String]()
let configuration = RTCConfiguration()
static let sharedInstance = WebRTC()
private override init() {
super.init()
configuration.iceServers = [RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"])]
}
deinit {
for peerConnection in peerConnections {
if let stream = peerConnection.value.localStreams.first {
peerConnection.value.remove(stream)
}
}
}
// MARK: inerface
func localView() -> UIView {
return _localView
}
func remoteView() -> UIView {
return _remoteView
}
func setup() {
setupLocalStream()
}
func connect(userID: String, isliveStreamUser: Bool = false, onCreatedLocalSdp: @escaping ((_ localSdp:NSDictionary) -> Void), didReceiveRemoteStream: @escaping ( () -> Void ) ) {
self.onCreatedLocalSdp = onCreatedLocalSdp
if isliveStreamUser == true {
self.didReceiveRemoteStream = didReceiveRemoteStream
}
peerConnectionUserList.append(userID)
peerConnections[userID] = factory.peerConnection(with: configuration, constraints: WebRTCUtil.peerConnectionConstraints(), delegate: self)
peerConnections[userID]?.add(localStream!)
}
func diconnect(userID: String, iceServerUrlList: [String], onCreatedLocalSdp: @escaping ((_ localSdp:NSDictionary) -> Void), didReceiveRemoteStream: @escaping ( () -> Void ) ) {
}
func flipCamera() {
if let mysource = localStream?.videoTracks.first?.source as? RTCAVFoundationVideoSource {
mysource.useBackCamera = !mysource.useBackCamera
}
}
func enableVideo() {
if let mysvideo = localStream?.videoTracks.first {
mysvideo.isEnabled = !mysvideo.isEnabled
}
}
func enableAudio() {
if let myAudio = localStream?.audioTracks.first {
myAudio.isEnabled = !myAudio.isEnabled
}
}
private func getPeerConnection(_ id: String) -> RTCPeerConnection? {
return peerConnections[id]
}
// Answer
func receiveAnswer(hostID: String, remoteSdp: NSDictionary) {
_receiveAnswer(hostID: hostID, remoteSdp: remoteSdp)
}
// Offer
func receiveOffer(joinUID: String, remoteSdp: NSDictionary) {
_receiveOffer(joinUID: joinUID, remoteSdp: remoteSdp)
}
// Offer
func createOffer(hostID: String) {
_createOffer(hostID: hostID)
}
// MARK: implements
private func _receiveAnswer(hostID: String, remoteSdp: NSDictionary) {
guard let sdpContents = remoteSdp.object(forKey: "sdp") as? String else {
print("noSDp")
return
}
let sdp = RTCSessionDescription(type: .answer, sdp: sdpContents)
// 1. remote SDP
peerConnections[hostID]?.setRemoteDescription(sdp, completionHandler: { (error) in print(105, error?.localizedDescription as Any) })
}
private func _receiveOffer(joinUID: String, remoteSdp: NSDictionary) {
guard let sdpContents = remoteSdp.object(forKey: "sdp") as? String else {
print("noSDp")
return
}
// 1. remote SDP
let remoteSdp = RTCSessionDescription(type: .offer, sdp: sdpContents)
peerConnections[joinUID]?.setRemoteDescription(remoteSdp, completionHandler: { (error) in
// if any user offer me i commend it for one user
// 2. answer
self.peerConnections[joinUID]?.answer(for: WebRTCUtil.answerConstraints(), completionHandler: { (sdp, error) in
guard let sdp = sdp else {
print("can not create sdp")
return
}
print(128, error?.localizedDescription as Any)
// 3.SDP
self.peerConnections[joinUID]?.setLocalDescription(sdp, completionHandler: { (error) in print(131, error?.localizedDescription as Any) })
// 4. peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState)
// complete Answer
})
})
}
private func getPeerId(_ peerConnection: RTCPeerConnection) -> String? {
for peerC in peerConnections {
if peerConnection == peerC.value {
return peerC.key
}
}
return nil
}
private func _createOffer(hostID: String) {
guard let peerConnection = getPeerConnection(hostID) else {
print("missing peer connection")
return
}
// 1. offer
print("hostUID", hostID)
peerConnection.offer(for: WebRTCUtil.mediaStreamConstraints(), completionHandler: { [weak self] (description, error) in
guard let description = description else {
print("----- no description ----")
return
}
print(111, error?.localizedDescription as Any)
// 2.SDP
peerConnection.setLocalDescription(description, completionHandler: { (error) in
guard self != nil else {
print("missin self")
return
}
print(185, error?.localizedDescription as Any)
})
print("createOffer 188 webrtc")
// 3. peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState)
// complete offer
})
}
private func setupLocalStream() {
let streamId = WebRTCUtil.idWithPrefix(prefix: "stream")
localStream = factory.mediaStream(withStreamId: streamId)
setupView()
setupLocalVideoTrack()
setupLocalAudioTrack()
}
private func setupView() {
localRenderView.delegate = self
_localView.backgroundColor = UIColor.white
_localView.frame.origin = CGPoint(x: 20, y: _remoteView.frame.size.height - (_localView.frame.size.height / 2))
_localView.addSubview(localRenderView)
remoteRenderView.delegate = self
_remoteView.backgroundColor = UIColor.lightGray
_remoteView.addSubview(remoteRenderView)
}
private func setupLocalVideoTrack() {
let localVideoSource = factory.avFoundationVideoSource(with: WebRTCUtil.mediaStreamConstraints())
let localVideoTrack = factory.videoTrack(with: localVideoSource, trackId: WebRTCUtil.idWithPrefix(prefix: "video"))
if let avSource = localVideoTrack.source as? RTCAVFoundationVideoSource {
avSource.useBackCamera = false
}
localVideoTrack.add(localRenderView)
localStream?.addVideoTrack(localVideoTrack)
}
private func setupLocalAudioTrack() {
let localAudioTrack = factory.audioTrack(withTrackId: WebRTCUtil.idWithPrefix(prefix: "audio"))
localStream?.addAudioTrack(localAudioTrack)
}
// MARK: RTCPeerConnectionDelegate
// いったんスルー
public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
print("peerConnectionShouldNegotiate")
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
print("stream", printLog, stream)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
print("didChange newState", printLog, newState)
switch newState {
case .checking:
print("[peerConnection didChangeConnectionState]:checking)")
case .closed:
print("[peerConnection didChangeConnectionState]:closed)")
case .completed:
print("[peerConnection didChangeConnectionState]:completed)")
case .connected:
print("[peerConnection didChangeConnectionState]:connected)")
case .disconnected:
print("[peerConnection didChangeConnectionState]:disconnected)")
case .failed:
print("[peerConnection didChangeConnectionState]:failed)")
default:
print("default")
}
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
print("didGenerate", printLog, candidate)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
print("didRemove", printLog, candidates)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
print("dataChannel 269", printLog, dataChannel)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
print("didChange stateChanged haveLocalOffer", stateChanged == .haveLocalOffer)
print("didChange stateChanged haveLocalPrAnswer", stateChanged == .haveLocalPrAnswer)
print("didChange stateChanged haveRemoteOffer", stateChanged == .haveRemoteOffer)
print("didChange stateChanged haveRemotePrAnswer", stateChanged == .haveRemotePrAnswer)
print("didChange stateChanged stable", stateChanged == .stable)
print("didChange stateChanged closed", stateChanged == .closed)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
print("peerConnection didAdd stream:")
if stream == localStream {
return
}
self.remoteStream = stream
if let remoteVideoTrack = stream.videoTracks.first {
DispatchQueue.main.async {
remoteVideoTrack.add(self.remoteRenderView)
}
}
if let callback = self.didReceiveRemoteStream {
DispatchQueue.main.async {
callback()
}
self.didReceiveRemoteStream = nil
}
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
print("peerConnection didChange newState: RTCIceGatheringState, \(newState)")
if newState != .complete {
return
}
print("1. userList", peerConnectionUserList)
for user in peerConnectionUserList {
print("2. user, self.peerConnection[user]", user, self.peerConnections[user] as Any)
if self.peerConnections[user] == peerConnection {
print("peerConnection connected", peerConnection)
guard let callback = self.onCreatedLocalSdp, let localDescription = WebRTCUtil.jsonFromDescription(description: self.peerConnections[user]?.localDescription) else {
print("no localDescription")
return
}
callback(localDescription)
self.onCreatedLocalSdp = nil
print("3. peerConnection didChange")
}
}
}
// MARK: RTCEAGLVideoViewDelegate
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
print("---- didChangeVideoSize -----")
let ratio:CGFloat = size.width / size.height
if videoView == localRenderView {
let parentWidth = _localView.frame.size.width
_ = parentWidth * ratio
// localRenderView.frame = CGRect(x: (parentWidth - width) / 2, y: 2, width: width, height: _localView.frame.size.height-4)
localRenderView.frame = CGRect(x: 0, y: 0, width: size.width, height: _localView.frame.size.height)
} else if videoView == remoteRenderView {
let parentWidth = _remoteView.frame.size.width
_ = parentWidth * ratio
// remoteRenderView.frame = CGRect(x: (parentWidth - width) / 2, y: 0, width: width, height: _remoteView.frame.size.height)
remoteRenderView.frame = CGRect(x: 0, y: 0, width: size.width, height: _remoteView.frame.size.height)
}
}
А вот мой VideoStreamVc.swift
import Foundation
import UIKit
class VideoStremingVC: UIViewController, WebSocketEventsListener {
private let webRTC = WebRTC.sharedInstance
private let controlButton = UIButton()
let username = PerfectLocalAuth.defaults.value(forKey: "username") as? String ?? ""
let avatar = PerfectLocalAuth.defaults.value(forKey: "profileImage") as? String ?? ""
@IBOutlet var btnStartLiveVideo: RoundButton!
// private let flipCameraButton = UIButton()
@IBOutlet weak var flipCameraButton: UIButton!
private var typeOffer: Bool = false
@IBOutlet weak var remoteView: UIView!
@IBOutlet weak var localView: UIView!
let socket = WebSocketTracker.shared
var testDP: NSDictionary!
var joinID: String?
@IBOutlet var startView: UIView!
@IBOutlet var topView: UIView!
@IBOutlet var bottomView: UIView!
override func viewDidLoad() {
super.viewDidLoad(); printTypeOf(self)
// Do any additional setup after loading the view, typically from a nib.
webRTC.setup()
localView.addSubview(webRTC.localView())
remoteView.addSubview(webRTC.remoteView())
self.tabBarController?.tabBar.isHidden = true
// view.addSubview(flipCameraButton)
socket.addListener(self)
socket.connect()
}
override func viewDidAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
//To change iOS 11 navigationBar largeTitle color
self.navigationController?.navigationBar.isHidden = true
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey(rawValue: NSAttributedStringKey.foregroundColor.rawValue): UIColor.white]
} else {
// for default navigation bar title color
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey(rawValue: NSAttributedStringKey.foregroundColor.rawValue): UIColor.white]
}
var newframe: CGRect = webRTC.localView().frame
newframe.size.width = localView.frame.size.width
newframe.size.height = localView.frame.size.height
newframe.origin.x = 0
newframe.origin.y = 0
webRTC.localView().frame = newframe
newframe = webRTC.remoteView().frame
newframe.size.width = remoteView.frame.size.width
newframe.size.height = remoteView.frame.size.height
newframe.origin.x = 0
newframe.origin.y = 0
webRTC.remoteView().frame = newframe
super.viewDidAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
socket.removeListener(self)
}
override var prefersStatusBarHidden: Bool {
return true
}
deinit {
socket.disconnect()
self.webRTC.diconnect(userID: PerfectLocalAuth.userid, iceServerUrlList: ["stun:stun.l.google.com:19302"], onCreatedLocalSdp: { (localSdp) in
let jsonString = HostRequest(type: "leave", hostID: PerfectLocalAuth.userid)
self.socket.socket.write(string: jsonString.jsonString ?? "")
print("inside self.webRTC.diconnect")
}, didReceiveRemoteStream: { () in })
let jsonString = HostRequest(type: "leave", hostID: PerfectLocalAuth.userid)
self.socket.socket.write(string: jsonString.jsonString ?? "")
print("deinit VideoStremingVC")
}
func dictToString(dict:NSDictionary) -> String? {
let jsonData = try? JSONSerialization.data(withJSONObject: dict, options: [])
let jsonString = String(data: jsonData!, encoding: .utf8)
return jsonString
}
func stringToDict(str:String) -> [String: Any]? {
let jsonData = str.data(using: .utf8)
let dictionary = try? JSONSerialization.jsonObject(with: jsonData!, options: .mutableLeaves)
print(dictionary!)
return dictionary as? [String : Any]
}
func dictToData(dict:NSDictionary) -> Data? {
let jsonData = try? JSONSerialization.data(withJSONObject: dict, options: [])
return jsonData
}
func dataToDict(data:Data) -> [String: Any]? {
let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
return dictionary as? [String : Any]
}
private func connect() {
webRTC.connect(userID: PerfectLocalAuth.userid, isliveStreamUser: false, onCreatedLocalSdp: { (localSdp) in
print("connected webrtc")
// {
// hostID:"123",
// joinuserID:"567",
// type:"offer", // offer duplicate so we can remove stream and change it to type
// sdp:"asdfasfgafgdfgfgasuysdfb78832jhbasd"
// }
guard let sdp = localSdp["sdp"] as? String else { return }
let jsonStringOffer = Stream(
id: "",
type: "offer",
username: "",
hostID: PerfectLocalAuth.userid,
joinUID: self.joinID,
avatarImageURL: "",
sdp: sdp
)
self.socket.socket.write(string: jsonStringOffer.jsonString ?? "")
print("create offer 164 VC")
}, didReceiveRemoteStream: { () in
print("connect didReceiveRemoteStream")
})
if typeOffer {
// webRTC.createOffer()
let jsonString = HostRequest(type: "host", hostID: PerfectLocalAuth.userid)
self.socket.socket.write(string: jsonString.jsonString ?? "")
APIClient.createVideoStreams(
id: nil,
hostID: PerfectLocalAuth.userid,
username: self.username,
avatarImageURL: self.avatar,
sdp: "",
type: "",
active: true
).execute(onSuccess: { videoStreamCreateResponse in
print("videoStreamCreateResponse", videoStreamCreateResponse)
}, onFailure: { videoStreamErrorResponse in
print("videoStreamErrorResponse", videoStreamErrorResponse)
})
} else {
// self.webRTC.receiveOffer(remoteSdp: self.testDP)
}
}
func connectFromHeventsVC(dict: NSDictionary) {
guard let hostID = dict["host_id"] as? String else {
print("dict missing"); printLine()
return
}
print("1 connectFromHeventsVC")
// print("connectFromHeventsVC dict offer from 183 line type", type)
webRTC.connect(userID: PerfectLocalAuth.userid, isliveStreamUser: true, onCreatedLocalSdp: { (localSdp) in
guard let sdp = localSdp["sdp"] as? String else { return }
print("connectFromHeventsVC localSdp", localSdp)
let jsonStringOffer = Stream(
id: "",
type: "answer",
username: "",
hostID: hostID,
joinUID: PerfectLocalAuth.userid,
avatarImageURL: "",
sdp: sdp
)
print("2 answer")
self.socket.socket.write(string: jsonStringOffer.jsonString ?? "")
}, didReceiveRemoteStream: { () in
print("connectFromHeventsVC didReceiveRemoteStream")
})
// {
// type:"approveme",
// hostID:"123",
// joinuserID:"567"
// }
let jsonStringOffer = Stream(
id: "",
type: "approveme",
username: "",
hostID: hostID,
joinUID: PerfectLocalAuth.userid,
avatarImageURL: "",
sdp: ""
)
print("3 approveme")
self.socket.socket.write(string: jsonStringOffer.jsonString ?? "")
}
// MARK: UIEventswe
@IBAction func flipCameraButton(_ sender: Any) {
self.webRTC.flipCamera()
}
// MARK: states
@IBAction func dissmissVC(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
@IBAction func btnStartLiveClicked(_ sender: Any) {
self.startView.isHidden = true
self.remoteView.isHidden = true
typeOffer = true
connect()
}
@IBAction func btnJoinLiveClicked(_ sender: Any) {
// self.localView.isHidden = true
// self.startView.isHidden = true
// typeOffer = false
// connect()
}
// MARK: Websocket Delegate Methods.
func webSocketEventsListenerDidConnect(_ webSocketTrackerService: WebSocketTracker) {
print("websocket is connected")
let jsonString = HostRequest(type: "connect", hostID: PerfectLocalAuth.userid)
self.socket.socket.write(string: jsonString.jsonString ?? "")
}
func webSocketEventsListenerDidDisconnect(_ webSocketTrackerService: WebSocketTracker, didDisconnectError: Error?) {
print("didDisconnect Error", didDisconnectError?.localizedDescription as Any)
}
func webSocketEventsListenerDidRecieveError(_ webSocketTrackerService: WebSocketTracker, didRecieveError: Error) {
print("didRecieveError", didRecieveError.localizedDescription)
}
func webSocketEventsListenerDidReceiveMessage(_ webSocketTrackerService: WebSocketTracker, didRecieveMessage text: String) {
print("Received text: \(text)")
guard let stream = Stream.from(json: text) else {
print("videoStreamingResponse json text error")
return
}
let hostID = stream.hostID ?? ""
let joinUID = stream.joinUID ?? ""
let sdp = stream.sdp ?? ""
let type = stream.type ?? ""
switch stream.type {
case "connect":
print("connect")
case "host":
print("steam type host")
case "approveme":
print("approveme hostID", hostID)
// create
webRTC.createOffer(hostID: hostID)
self.joinID = joinUID
case "offer":
print("steam type offer")
let offer: [String: String] = [
"type": type,
"sdp": sdp
]
webRTC.receiveOffer(joinUID: joinUID, remoteSdp: offer as NSDictionary)
case "answer":
print("answer")
guard let answer = ["sdp": sdp, "type": type] as? NSDictionary else { return }
webRTC.receiveAnswer(hostID: hostID, remoteSdp: answer)
case "leave":
print("leave")
case "end":
print("end")
default:
print("stream type default")
}
}
func webSocketEventsListenerDidReceiveData(_ webSocketTrackerService: WebSocketTracker, didRecieveMessage data: Data) {
// print("Received data count: \(data.count)")
// print("Received data: \(data)")
// print("onReceiveOffer")
// let dict = self.dataToDict(data: data)! as NSDictionary
//
// if dict["type"] as? String == "answer" {
//
// if !self.typeOffer { return }
// print("onReceiveAnswer")
// self.webRTC.receiveAnswer(remoteSdp: dict)
//
// } else {
//
// if self.typeOffer { return }
// self.testDP = dict
//// self.webRTC.receiveOffer(remoteSdp: dict)
// }
}
func sendMessage(_ message: String) {
print("message", message)
}
func register(_ response: String) {
print("respose", response)
}
// MARK: Write Text Action
@IBAction func writeText(_ sender: UIBarButtonItem) {
// socket.write(string: "hello there!") need to rewrite
}
}
- Пользователь в прямом эфире
- 1-й пользователь может присоединиться и смотреть только пользователя в прямом эфире
- 2-й пользователь присоединяется, но после создания предложения он не отправляет предложение присоединиться к пользователю через WebSocket
Пользователь прямой трансляции создает предложение, но не отправляет, поскольку его невозможно подключить, возможно, сwebRTC - кто-нибудь может помочь пройти через мой код и проверить, что не так в моем коде, где я делаю неправильно?спасибо, Advance
У меня есть несколько журналов, чтобы проверить, что происходит, когда я запускаю свой Xcode, работающий с iPhone (который сейчас делится своим живым видео)
Журналы присоединения 1-го пользователя
введите описание ссылки здесь
журналы регистрации второго пользователя
введите описание ссылки здесь