Skyway SDKを使ったiOS版通話アプリ開発
はじめに
モバイル版の音声通話アプリを開発するには、サーバー構築、API作成、各端末への接続設定といった色々な手段や時間を費やします。幸いにも、SkywayやTwilioなどのフレームワークのサポートを受けることができます。まず、Skywayについてお話しします。Twilloについて後述させていただきます。
概して言えば、Skywayは、ビデオ通話機能や音声通話機能をかんたんにアプリに導入できるマルチプラットフォームのSDKであり、フルマネージドなAPIサービスです。詳細につきましてはSkyway SDKのホームページにてご参照ください。
この記事では、Skyway SDKを使ったiOS版通話アプリの開発を紹介させていただきます。
SkywayホームページでのAPIキー作成
登録してからSkywayコンソールにログインします。
コンソールのページで、「DemoSkywayApp」という新しいアプリケーションを作成します。
作成後、iOSで使用できるようにAPIキーとドメインをある場所に保存しておいてください。
iOSプロジェクト作成
「DemoChatApp」という新しいプロジェクトを作成します。
CocoaPodsの構成
PodFileファイルを作成し、プロジェクトのルートディレクトリに配置して、次のコードをそのファイルに入れます。
platform :ios,'9.0' use_frameworks! def install_pods pod 'SkyWay' pod 'MBProgressHUD' #for toast message end target 'DemoChatApp' do install_pods end
次に、ターミナルアプリを開き、プロジェクトのルートディレクトリで次のコマンドを実行することでSkywayをインストールします。
pod install
アプリ側は音声通話のため端末のマイクを使用するので、端末のマイクにアクセスする権限をinfo.plistファイルに追加する必要があります。
NSMicrophoneUsageDescription This sample uses a mic device for voice chatap
Skyway導入
まず、PeerServerへの接続設定、ルームへの参加・退出などを処理するコントローラーを作成します。
import UIKit import SkyWay import MBProgressHUD let SKYWAY_APIKEY = ""//Skywayコンソールページから取得したAPIキー let SKYWAY_DOMAIN = ""//Skywayコンソールページから取得したドメイン 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 //PeerServerへの接続の起動 func connectToPeerServer(handler:ConnectionHandler? = nil) { let options = SKWPeerOption() options.key = SKYWAY_APIKEY; options.domain = SKYWAY_DOMAIN; currentPeer = SKWPeer.init(options: options) //PeerServerへの接続が設定された時のイベント処理 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) }) //PeerServerへの接続がキャンセルされ、繋がらなくなった時のイベント処理 currentPeer?.on(.PEER_EVENT_CLOSE, callback: { (obj) in print("PEER_EVENT_CLOSE \(obj!)") //端末からPeerServerへのデータストリームのキャンセル self.destroyLocalStream() handler?(false) }) //ユーザーによる取消などの何らかの理由でPeerServerへの接続が切断された時のイベント処理 currentPeer?.on(.PEER_EVENT_DISCONNECTED, callback: { (obj) in print("PEER_EVENT_DISCONNECTED \(obj!)") self.connected = false self.destroyLocalStream() handler?(false) }) //通信回線問題などが原因で、PeerServerへの接続が出来なくなった時のイベント処理 currentPeer?.on(.PEER_EVENT_ERROR, callback: { (obj) in print("PEER_EVENT_ERROR \(obj!)") self.connected = false self.destroyLocalStream() handler?(false) }) } //特定ルームへの参加 func joinRoom(roomName:String, handler:@escaping RoomStatusUpdatedHandler) { //まだPeerServerに接続していない場合は、事前に接続しておいてください。 if(!connected){ self.connectToPeerServer { (flag) -> (Void) in if(flag){ self.joinRoom(roomName: roomName, handler: handler) } } return } //ルーム構成 let option = SKWRoomOption() option.mode = .ROOM_MODE_MESH option.stream = localStream //ルーム初期化 currentRoom = currentPeer?.joinRoom(withName: roomName, options: option) as? SKWMeshRoom //イベント処理 //ルームに正常に参加する時のイベント処理 currentRoom?.on(.ROOM_EVENT_OPEN, callback: { (obj) in print("room \(roomName) opened") handler(.Opened) }) //ルームを出る時のイベント処理 currentRoom?.on(.ROOM_EVENT_CLOSE, callback: { (obj) in self.currentRoom?.offAll() self.currentRoom = nil handler(.Closed) }) //他者が追加でルームに参加する時のイベント処理 currentRoom?.on(.ROOM_EVENT_PEER_JOIN, callback: { (obj) in let message = "peerId \(obj as! String) joined call" self.toastMessage(message: message) handler(.OtherJoined) }) //他者がルームを出る時のイベント処理 currentRoom?.on(.ROOM_EVENT_PEER_LEAVE, callback: { (obj) in let message = "peerId \(obj as! String) left call" self.toastMessage(message: message) handler(.OtherLeft) }) //ルームへの接続が設定できない時のイベント処理 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//このサンプルではビデオ通話機能を使わず音声通話機能のみ使うので、videoFlagを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) } } }
以下のように主な関数が三つあります。
- connectToPeerServer()
- joinRoom()
- leaveRoom()
connectToPeerServer() はSkywayのPeerServerへの接続を設定できるように最初に呼び出す必要があります。返却された値はkZoHgbFfTtgBeQhpのような文字列が自分のpeerIdです。
この関数では、次のイベント処理を行う必要があります。
- PEER_EVENT_OPEN:PeerServerへの接続に成功した時、データ(音声)を送信するようにPeerServerへのストリーム接続を生成します。この記事では、音声通話のみに焦点を当てるため、videoFlagを無効にしておいてください。(デフォルトはYES)
- PEER_EVENT_CLOSE:繋がらなくなった時、ストリームへの接続を切断し、またルームに接続している場合、ルームへの接続も切断します。
- PEER_EVENT_DISCONNECTED:ユーザーが接続を失った場合、上記のイベントと同じ操作を行います。
- PEER_EVENT_ERROR:接続にエラーがあった場合、上記のイベントと同じ操作を行います。
joinRoom()はルームに参加するときにルーム名をパラメーターとして呼び出されます。なお、ルーム名は任意に命名できますが、者同士しかルームに参加できないように、ユニークな名前を選択した方がいいです。 これはサーバー側で、ユニークな名前を生成するように処理した方がいいです。
また、connectToPeerServer()関数と同様に、次のイベント処理を行う必要があります。
- ROOM_EVENT_OPEN:ルームに参加する時
- ROOM_EVENT_CLOSE:ルームを出る時
- ROOM_EVENT_PEER_JOIN:他者がルームに参加する時
- ROOM_EVENT_PEER_LEAVE:他者がルームを出る時
- ROOM_EVENT_ERROR:ネットワークまたはタイムアウト等でルームへの接続にエラーが発生する時
あと、以下のような簡単な画面のCallViewControllerビューコントローラーを作成します。
CallViewController.swiftファイルに、次のコードを入れます。
import UIKit import SkyWay class CallViewController: UIViewController { @IBOutlet weak var joinButton: UIButton! @IBOutlet weak var roomTxt: UITextField! //ルームのステータスが変わったら、ボタンのタイトルが更新されます。 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() } } }
ユーザーが参加したいルームの名前を入力するためのテキストフィールドと、ルームに参加または退室するためのボタンがあります。
こうすることで2台のiOS端末で実行して同じルームに参加して動作を確認することができます。
尚、このアプリはシミュレーターに対応していません。
参照元