Tạo ứng dụng gọi thoại cho iOS sử dụng skyway SDK
Lời giới thiệu
Khi phát triển 1 ứng dụng chat voice cho điện thoại, chúng ta phải xây dựng rất nhiều thứ, từ server, viết API kết nối các thiết bị,….May mắn thay, gần đây chúng ta đã có 1 số framework làm hầu hết công việc đó như là Skyway, Twilio. Hôm nay chúng ta sẽ tập trung vào Skyway và sẽ nói về Twillo trong 1 bài viết khác.
Tổng quan, Skyway là 1 SDK đa nền tảng và đầy đủ dịch vụ API quản lý khiến việc xây dựng tính năng chat video hay voice chat thật đơn giản, bạn có thể tìm hiểu thêm trên trang chủ của Skyway SDK .Trong bài viết này, chúng tôi sẽ hướng dẫn các bạn làm thế nào để xây dựng 1 ứng dụng voice chat trên iOS.
Tạo ApiKey trên trang Skyway
Đăng ký và login vào Skyway console.
Tại trang chủ của console tạo 1 ứng dụng mới tên là “DemoSkywayApp”
Sau khi tạo xong hãy lưu api key và domain bạn chọn vào 1 nơi nào đó để có thể sử dụng cho phía iOS.
Xây dựng dự án iOS
Tạo 1 dự án mới tên là “DemoChatApp”
Cấu hình CocoaPods
Tạo 1 file tên là PodFile và đặt trong thư mục root của dự án và dán đoạn code sau vào
platform :ios,'9.0' use_frameworks! def install_pods pod 'SkyWay' pod 'MBProgressHUD' #for toast message end target 'DemoChatApp' do install_pods end
Sau đó mở ứng dụng Terminal và chạy lệnh sau ở thư mục root của dự án để cài đặt Skyway
pod install
Bởi vì ứng dụng sẽ sử dụng micro của thiết bị nên chúng ta cần thêm quyền truy cập micro vào file info.plist của dự án.
NSMicrophoneUsageDescription This sample uses a mic device for voice chatap
Áp dụng Skyway
Trước tiên tạo 1 controller tên là để xử lý các việc như thiết lập kết nối tới PeerServer, gia nhập phòng hay rời phòng.
import UIKit import SkyWay import MBProgressHUD let SKYWAY_APIKEY = ""//api key từ trang skyway console let SKYWAY_DOMAIN = ""//domain từ trang skyway console let kSkywayController = SkywayController.shared public enum RoomStatus{ case Opened case Joined case Left case OtherJoined case OtherLeft case Disconnected case Closed case Error } public typealias RoomStatusUpdatedHandler = (RoomStatus) -> (Void) public typealias ConnectionHandler = (Bool) -> (Void) class SkywayController: NSObject { static let shared = SkywayController() public var currentPeer:SKWPeer? = nil public var localStream:SKWMediaStream? = nil public var currentRoom:SKWMeshRoom? = nil private var connected = false //Khởi tạo kết nối tới PeerServer func connectToPeerServer(handler:ConnectionHandler? = nil) { let options = SKWPeerOption() options.key = SKYWAY_APIKEY; options.domain = SKYWAY_DOMAIN; currentPeer = SKWPeer.init(options: options) //Xử lý sự kiện khi kết nối tới PeerServer đã được thiết lập currentPeer?.on(.PEER_EVENT_OPEN, callback: { (obj) in print("PEER_EVENT_OPEN \(obj!)") //init data stream from local to PeerServer self.connected = true self.initLocalStream() handler?(true) }) //Xử lý sự kiện khi kết nối tới PeerServer bị huỷ và không còn kết nối được nữa currentPeer?.on(.PEER_EVENT_CLOSE, callback: { (obj) in print("PEER_EVENT_CLOSE \(obj!)") //Huỷ stream data từ thiết bị tới PeerServer self.destroyLocalStream() handler?(false) }) //Xử lý sự kiện khi bị mất kết nối tới PeerServer, có thể do user tự huỷ currentPeer?.on(.PEER_EVENT_DISCONNECTED, callback: { (obj) in print("PEER_EVENT_DISCONNECTED \(obj!)") self.connected = false self.destroyLocalStream() handler?(false) }) //Xử lý sự kiện khi không thể kết nối tới PeerServer, có thể do đường truyền có vấn đề currentPeer?.on(.PEER_EVENT_ERROR, callback: { (obj) in print("PEER_EVENT_ERROR \(obj!)") self.connected = false self.destroyLocalStream() handler?(false) }) } // Tham gia vào 1 room cụ thể func joinRoom(roomName:String, handler:@escaping RoomStatusUpdatedHandler) { //Nếu chưa kết nối tới PeerServer, hãy kết nối trước if(!connected){ self.connectToPeerServer { (flag) -> (Void) in if(flag){ self.joinRoom(roomName: roomName, handler: handler) } } return } //Cấu hình room let option = SKWRoomOption() option.mode = .ROOM_MODE_MESH option.stream = localStream //Khởi tạo room currentRoom = currentPeer?.joinRoom(withName: roomName, options: option) as? SKWMeshRoom //Xử lý các sự kiện //Sự kiện khi chúng ta tham gia vào room thành công currentRoom?.on(.ROOM_EVENT_OPEN, callback: { (obj) in print("room \(roomName) opened") handler(.Opened) }) //Sự kiện khi chúng ta rời khỏi room currentRoom?.on(.ROOM_EVENT_CLOSE, callback: { (obj) in self.currentRoom?.offAll() self.currentRoom = nil handler(.Closed) }) //Sự kiện khi 1 người khác tham gia vào room currentRoom?.on(.ROOM_EVENT_PEER_JOIN, callback: { (obj) in let message = "peerId \(obj as! String) joined call" self.toastMessage(message: message) handler(.OtherJoined) }) //Sự kiện khi 1 người khác rời khỏi room currentRoom?.on(.ROOM_EVENT_PEER_LEAVE, callback: { (obj) in let message = "peerId \(obj as! String) left call" self.toastMessage(message: message) handler(.OtherLeft) }) //Sự kiện khi không thể thiết lập kết nối tới room currentRoom?.on(.ROOM_EVENT_ERROR, callback: { (obj) in print("ROOM_EVENT_ERROR \(obj)") handler(.Error) }) } func leaveRoom() { self.currentRoom?.close() self.currentRoom?.offAll() } // MARK: - Private methods private func initLocalStream(){ let constraint = SKWMediaConstraints() constraint.audioFlag = true constraint.videoFlag = false//trong bài viết này chúng ta chỉ sử dụng voice chat nên hãy set videoFlag thành false SKWNavigator.initialize(currentPeer!) localStream = SKWNavigator.getUserMedia(constraint) } private func destroyLocalStream(){ SKWNavigator.terminate() localStream = nil leaveRoom() } private func toastMessage(message:String){ print("toastMessage \(message)") DispatchQueue.main.asyncAfter(deadline: .now() ) { let hud = MBProgressHUD.showAdded(to: UIApplication.shared.keyWindow!, animated: true) hud.mode = .text hud.label.text = message hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: 2) } } }
Chúng ta có 3 hàm chính:
- connectToPeerServer()
- joinRoom()
- leaveRoom()
connectToPeerServer() cần được gọi đầu tiên để thiết lập kết nối tới PeerServer của Skyway, giá trị nhận về là peerId của chính chúng ta, đó là 1 chuỗi đại loại như kZoHgbFfTtgBeQhp. Trong hàm này chúng ta cần áp dụng các xử lý sự kiện sau:
- PEER_EVENT_OPEN: Khi nối tới PeerServer thành công, tạo 1 kết nối stream tới PeerServer để truyền dữ liệu(âm thanh) lên đó, ở bài viết này chỉ tập trung vào voice chat vì vậy hãy disable biến videoFlag (mặc định là YES) .
- PEER_EVENT_CLOSE: Khi user đóng kết nối hãy huỷ kết nối stream và kết nối tới room nếu có.
- PEER_EVENT_DISCONNECTED: Khi user mất kết nối, hãy làm thao tác tương tự như event trên.
- PEER_EVENT_ERROR: Khi kết nối bị lỗi, hãy làm thao tác tương tự như event trên.
joinRoom() được gọi khi bạn tham gia 1 room, tham số là tên room(có thể sử dụng tuỳ ý bất kỳ tên nào mà bạn muốn, nhưng bạn nên chọn 1 tên duy nhất để không ai có thể tham gia vào room ngoài người bạn mong muốn, việc này nên xử lý ở phía server để tạo 1 chuỗi duy nhất). Và tương tự như hàm connectToPeerServer(), bạn cần xử lý thêm các sự kiện sau:
- ROOM_EVENT_OPEN: Khi chúng ta tham gia room
- ROOM_EVENT_CLOSE: Khi chúng ta rời room
- ROOM_EVENT_PEER_JOIN: Khi người khác tham gia room
- ROOM_EVENT_PEER_LEAVE: Khi người khác rời room
- ROOM_EVENT_ERROR: Khi kết nối tới room bị lỗi(do mạng hay timeout,…)
Sau đó chúng tạo 1 view controller tên là CallViewController có giao diện đơn giản như sau:
Trong file CallViewController.swift , hãy dán đoạn code sau vào
import UIKit import SkyWay class CallViewController: UIViewController { @IBOutlet weak var joinButton: UIButton! @IBOutlet weak var roomTxt: UITextField! //Khi trạng thái của room thay đổi, cập nhật tiêu đề của button private var joined = false{ didSet{ if joined { joinButton.setTitle("End call", for: .normal) }else{ joinButton.setTitle("Join", for: .normal) } } } override func viewDidLoad() { super.viewDidLoad() kSkywayController.connectToPeerServer() } @IBAction func joinRoom(_ sender: Any) { if !joined{ kSkywayController.joinRoom(roomName: roomTxt.text!) { (status) -> (Void) in if status == .Opened { self.joined = true } if status == .Closed || status == .Disconnected || status == .Error || status == .OtherLeft{ self.joined = false } } }else{ kSkywayController.leaveRoom() } } }
Chúng ta đơn giản có 1 textfield để user nhập tên room mong muốn tham gia, với 1 button để tham gia hay rời room.
Giờ hãy chạy project trên 2 thiết bị iOS (ứng dụng này không hỗ trợ simulator), cùng kết nối 1 room và xem nó hoạt động như thế nào nhé.
Tham khảo: