[{"data":1,"prerenderedAt":284},["ShallowReactive",2],{"news-item-\u002Fnews\u002Fvideochat-skyway":3},{"id":4,"title":5,"body":6,"category":270,"created by":273,"date":274,"description":17,"extension":275,"meta":276,"navigation":277,"path":278,"sections":279,"seo":280,"stem":281,"thumbnail":282,"__hash__":283},"content_en\u002Fnews\u002Fvideochat-skyway.md","Create voice chat app using Skyway SDK in iOS",{"type":7,"value":8,"toc":264},"minimark",[9,14,18,29,33,42,51,61,64,68,72,75,79,85,88,98,101,107,110,114,117,123,128,134,140,143,156,166,184,193,210,217,222,225,231,242,245,250,258],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"When developing a chat app in mobile, we have to setup many things from set-up server, write API to set up connections between users...Fortunately, we have some frameworks support we do that like Skyway, Twilio,.. This post will focus on Skyway, we will talk about Twilio in a different post.",[15,19,20,21,28],{},"Overview, SkyWay is a multi-platform SDK & fully managed API service that makes it easy to implement video and voice calling functionality in your applications as described in ",[22,23,27],"a",{"href":24,"rel":25},"https:\u002F\u002Fwebrtc.ecl.ntt.com\u002Fen\u002F",[26],"nofollow","home page"," . It supports for building chat applications in iOS, Android, Web platforms. In this tutorial, we will show you how to implement on iOS.",[10,30,32],{"id":31},"create-apikey-in-skyway-site","Create ApiKey in Skyway site",[15,34,35,36,41],{},"Signup and login to ",[22,37,40],{"href":38,"rel":39},"https:\u002F\u002Fconsole-webrtc-free.ecl.ntt.com\u002Fusers\u002Flogin",[26],"Skyway console",".",[15,43,44,45,50],{},"In ",[22,46,49],{"href":47,"rel":48},"https:\u002F\u002Fconsole-webrtc-free.ecl.ntt.com\u002F",[26],"console page",", create a new application named \"DemoSkywayApp\"",[52,53],"img",{"className":54,"alt":58,"src":59,"style":60},[55,56,57],"block","mx-auto","mb-4","","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16110849\u002Fcreate_app-1024x889.png","width: 100%;",[15,62,63],{},"After app created, save api key and your domain in somewhere so that you can use in client side.",[52,65],{"className":66,"alt":58,"src":67,"style":60},[55,56,57],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16111503\u002FScreen-Shot-2020-07-16-at-11.14.44-1024x520.png",[10,69,71],{"id":70},"setup-ios-project","Setup iOS project",[15,73,74],{},"Create a project called \"DemoChatApp\"",[52,76],{"className":77,"alt":58,"src":78,"style":60},[55,56,57],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16104243\u002Fcreate_ios_prj-1024x426.png",[15,80,81],{},[82,83,84],"strong",{},"Configure CocoaPods",[15,86,87],{},"Create a file named PodFile put in root project's directory and paste following commands:",[89,90,95],"pre",{"className":91,"code":93,"language":94},[92],"language-text","platform :ios,'9.0'\nuse_frameworks!\ndef install_pods\n  pod 'SkyWay'\n  pod 'MBProgressHUD' #for toast message\nend\n\ntarget 'DemoChatApp' do\n  install_pods\nend\n","text",[96,97,93],"code",{"__ignoreMap":58},[15,99,100],{},"Open Terminal app then run following command:",[89,102,105],{"className":103,"code":104,"language":94},[92],"pod install\n",[96,106,104],{"__ignoreMap":58},[15,108,109],{},"at root project's directory to install Skyway",[52,111],{"className":112,"alt":58,"src":113,"style":60},[55,56,57],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16105409\u002Fpod_install-1024x239.png",[15,115,116],{},"Because our application need to use micro for voice chat so we need to add micro permission to info.plist",[89,118,121],{"className":119,"code":120,"language":94},[92],"NSMicrophoneUsageDescription\nThis sample uses a mic device for voice chat\n",[96,122,120],{"__ignoreMap":58},[15,124,125],{},[82,126,127],{},"Implement Skyway",[15,129,130,131],{},"First we need a controller to handle setup connect to peer server, join room,.. named ",[82,132,133],{},"SkywayController.swift",[89,135,138],{"className":136,"code":137,"language":94},[92],"import UIKit\nimport SkyWay\nimport MBProgressHUD\n\nlet SKYWAY_APIKEY = \"\"\u002F\u002Fapi key from skyway console\nlet SKYWAY_DOMAIN = \"\"\u002F\u002Fdomain from skyway console\n\nlet kSkywayController = SkywayController.shared\npublic enum RoomStatus{\n    case Opened\n    case Joined\n    case Left\n    case OtherJoined\n    case OtherLeft\n    case Disconnected\n    case Closed\n    case Error\n}\n\npublic typealias RoomStatusUpdatedHandler = (RoomStatus) -> (Void)\npublic typealias ConnectionHandler = (Bool) -> (Void)\n\nclass SkywayController: NSObject {\n    static let shared = SkywayController()\n    \n    public var currentPeer:SKWPeer? = nil\n    public var localStream:SKWMediaStream? = nil\n    public var currentRoom:SKWMeshRoom? = nil\n    \n    private var connected = false\n    \n    \u002F\u002FInitalize to connect to PeerServer\n    func connectToPeerServer(handler:ConnectionHandler? = nil) {\n        let options = SKWPeerOption()\n        options.key = SKYWAY_APIKEY;\n        options.domain = SKYWAY_DOMAIN;\n        currentPeer = SKWPeer.init(options: options)\n        \n        \u002F\u002Fhandle event when connection to the PeerServer is established\n        currentPeer?.on(.PEER_EVENT_OPEN, callback: { (obj) in\n            print(\"PEER_EVENT_OPEN \\(obj!)\")\n            \u002F\u002Finit data stream from local to PeerServer\n            self.connected = true\n            self.initLocalStream()\n            handler?(true)\n        })\n                \n        \u002F\u002Fhandle event when the peer is destroyed and can no longer accept or create any new connections\n        currentPeer?.on(.PEER_EVENT_CLOSE, callback: { (obj) in\n            print(\"PEER_EVENT_CLOSE \\(obj!)\")\n            \u002F\u002Fdestroy data stream from local to PeerServer\n            self.destroyLocalStream()\n            handler?(false)\n        })\n        \n        \u002F\u002Fhandle event when the peer is disconnected from the signalling server, either manually or because the connection to the signalling server was lost.\n        currentPeer?.on(.PEER_EVENT_DISCONNECTED, callback: { (obj) in\n            print(\"PEER_EVENT_DISCONNECTED \\(obj!)\")\n            self.connected = false\n            self.destroyLocalStream()\n            handler?(false)\n        })\n        \n        \u002F\u002Fhandle event when errors on the peer are almost always fatal and will destroy the peer.\n        currentPeer?.on(.PEER_EVENT_ERROR, callback: { (obj) in\n            print(\"PEER_EVENT_ERROR \\(obj!)\")\n            self.connected = false\n            self.destroyLocalStream()\n            handler?(false)\n        })\n        \n    }\n    \n    \u002F\u002Fjoin to a specific room\n    func joinRoom(roomName:String, handler:@escaping RoomStatusUpdatedHandler) {\n        \u002F\u002Fif not connected, try re-connect to connectToPeerServer\n        if(!connected){\n            self.connectToPeerServer { (flag) -> (Void) in\n                if(flag){\n                    self.joinRoom(roomName: roomName, handler: handler)\n                }\n            }\n            return\n        }\n        \u002F\u002Fset room's options\n        let option = SKWRoomOption()\n        option.mode = .ROOM_MODE_MESH\n        option.stream = localStream\n        \n        \u002F\u002Finit room with roomName as parameter\n        currentRoom = currentPeer?.joinRoom(withName: roomName, options: option) as? SKWMeshRoom\n        \u002F\u002Fsetup event handlers\n        \n        \u002F\u002Fevent when we join the room\n        currentRoom?.on(.ROOM_EVENT_OPEN, callback: { (obj) in\n            print(\"room \\(roomName) opened\")\n            handler(.Opened)\n        })\n        \n        \u002F\u002Fevent when we left the room\n        currentRoom?.on(.ROOM_EVENT_CLOSE, callback: { (obj) in\n            self.currentRoom?.offAll()\n            self.currentRoom = nil\n            handler(.Closed)\n        })\n                \n        \u002F\u002Fevent when other member joined the room\n        currentRoom?.on(.ROOM_EVENT_PEER_JOIN, callback: { (obj) in\n            let message = \"peerId \\(obj as! String) joined call\"\n            self.toastMessage(message: message)\n            handler(.OtherJoined)\n        })\n        \n        \u002F\u002Fevent when other member left the room\n        currentRoom?.on(.ROOM_EVENT_PEER_LEAVE, callback: { (obj) in\n            let message = \"peerId \\(obj as! String) left call\"\n            self.toastMessage(message: message)\n            handler(.OtherLeft)\n        })\n             \n        currentRoom?.on(.ROOM_EVENT_ERROR, callback: { (obj) in\n            print(\"ROOM_EVENT_ERROR \\(obj)\")\n            handler(.Error)\n        })\n    }\n    \n    func leaveRoom()  {\n        self.currentRoom?.close()\n        self.currentRoom?.offAll()\n    }\n    \u002F\u002F MARK: - Private methods\n    private func initLocalStream(){\n        let constraint = SKWMediaConstraints()\n        constraint.audioFlag = true\n        constraint.videoFlag = false\u002F\u002Fin this tutorial we just use voice chat, so we skip videoFlag\n        SKWNavigator.initialize(currentPeer!)\n        localStream = SKWNavigator.getUserMedia(constraint)\n    }\n    \n    private func destroyLocalStream(){\n        SKWNavigator.terminate()\n        localStream = nil\n        leaveRoom()\n    }\n    \n    private func toastMessage(message:String){\n        print(\"toastMessage \\(message)\")\n        DispatchQueue.main.asyncAfter(deadline: .now() ) {\n            let hud = MBProgressHUD.showAdded(to: UIApplication.shared.keyWindow!, animated: true)\n            hud.mode = .text\n            hud.label.text = message\n            hud.removeFromSuperViewOnHide = true\n            hud.hide(animated: true, afterDelay: 2)\n        }\n    }        \n}\n",[96,139,137],{"__ignoreMap":58},[15,141,142],{},"We have 3 main functions:",[144,145,146,150,153],"ul",{},[147,148,149],"li",{},"connectToPeerServer()",[147,151,152],{},"joinRoom()",[147,154,155],{},"leaveRoom()",[15,157,158,161,162,165],{},[82,159,160],{},"connectToPeerServer","() need to be called first to establish connection with PeerServer of Skyway, it will return a string called ",[82,163,164],{},"peerId,"," which a string like this kZoHgbFfTtgBeQhp, then you need to implement call back events:",[144,167,168,175,178,181],{},[147,169,170,171,174],{},"PEER_EVENT_OPEN: When user connection established successfully, set a stream connection from our device to PeerServer to send data(voice data) to PeerServer, because in this tutorial we just implement voice chat so we disable ",[82,172,173],{},"videoFlag"," (default is YES).",[147,176,177],{},"PEER_EVENT_CLOSE: When user lost connection, destroy local data stream and room connection also if connected a room.",[147,179,180],{},"PEER_EVENT_DISCONNECTED: When user disconnected, handle same like above event.",[147,182,183],{},"PEER_EVENT_ERROR: When connection got error, handle same like above event.",[15,185,186,189,190,192],{},[82,187,188],{},"joinRoom","() is called when you expect to join a room, parameter is room's name (you can use any name as you want, but you should choose a unique name so no one else can join, this step should be handled in server to render unique string). And like ",[82,191,160],{},"(), you need to handle call back events:",[144,194,195,198,201,204,207],{},[147,196,197],{},"ROOM_EVENT_OPEN: When we joined room.",[147,199,200],{},"ROOM_EVENT_CLOSE: When we left room.",[147,202,203],{},"ROOM_EVENT_PEER_JOIN: When other users joined room.",[147,205,206],{},"ROOM_EVENT_PEER_LEAVE: When other users left room.",[147,208,209],{},"ROOM_EVENT_ERROR: When connection to room got error (disconnected or timeout problem)",[15,211,212,213,216],{},"Then we create a sample view controller named ",[82,214,215],{},"CallViewController"," like following",[52,218],{"className":219,"alt":58,"src":220,"style":221},[55,56,57],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16165958\u002FIMG_9227-576x1024.png","width: 50%;",[15,223,224],{},"in CallViewController.swift we include following code",[89,226,229],{"className":227,"code":228,"language":94},[92],"import UIKit\nimport SkyWay\n\nclass CallViewController: UIViewController {\n    \n    @IBOutlet weak var joinButton: UIButton!\n    @IBOutlet weak var roomTxt: UITextField!\n        \n    \u002F\u002Fwhen joined status update, update button title also\n    private var joined = false{\n        didSet{\n            if joined {\n                joinButton.setTitle(\"End call\", for: .normal)\n            }else{\n                joinButton.setTitle(\"Join\", for: .normal)\n            }\n        }\n    }\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        kSkywayController.connectToPeerServer()        \n    }\n    \n    @IBAction func joinRoom(_ sender: Any) {\n        if !joined{\n            kSkywayController.joinRoom(roomName: roomTxt.text!) { (status) -> (Void) in\n                if status == .Opened {\n                    self.joined = true\n                }\n                if status == .Closed || status == .Disconnected || status == .Error || status == .OtherLeft{\n                    self.joined = false\n                }\n            }\n        }else{\n            kSkywayController.leaveRoom()\n        }\n        \n    }\n        \n}\n",[96,230,228],{"__ignoreMap":58},[15,232,233,234,237,238,241],{},"Simply, we have a textfield so that user can input room's name, with a button to ",[82,235,236],{},"Join"," or ",[82,239,240],{},"End Call"," (we detect the room's status has changed to update button's title and call function joinRoom() or leaveRoom() properly)",[15,243,244],{},"Now you run project in 2 device (simulator not supported) connected with the same room name to check how it works.",[15,246,247],{},[82,248,249],{},"Refs:",[15,251,252,257],{},[22,253,256],{"href":254,"rel":255},"https:\u002F\u002Fgithub.com\u002Fskyway\u002Fskyway-ios-sdk",[26],"Skyway iOS SDK"," ",[15,259,260],{},[22,261,263],{"href":24,"rel":262},[26],"Skyway Homepage",{"title":58,"searchDepth":265,"depth":265,"links":266},2,[267,268,269],{"id":12,"depth":265,"text":13},{"id":31,"depth":265,"text":32},{"id":70,"depth":265,"text":71},[271,272],"tech talk","mobile","Briswell Vietnam Co Ltd","2020-08-10","md",{},true,"\u002Fnews\u002Fvideochat-skyway",null,{"title":5,"description":17},"news\u002Fvideochat-skyway","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F08\u002F10091008\u002Fskyway-log.png","qnVQIZBVH9hincQG3kNMg-NN1p48j8mmxsl3pR9nLek",1785320522592]