Skyway SDKを使ったiOS版通話アプリ開発

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端末で実行して同じルームに参加して動作を確認することができます。
尚、このアプリはシミュレーターに対応していません。

参照元

Skyway iOS SDK 

Skyway Homepage