[{"data":1,"prerenderedAt":261},["ShallowReactive",2],{"news-item-\u002Fja\u002Fnews\u002Fvideochat-skyway":3},{"id":4,"title":5,"body":6,"category":247,"created by":250,"date":251,"description":16,"extension":252,"meta":253,"navigation":254,"path":255,"sections":256,"seo":257,"stem":258,"thumbnail":259,"__hash__":260},"content_ja\u002Fja\u002Fnews\u002Fvideochat-skyway.md","Skyway SDKを使ったiOS版通話アプリ開発",{"type":7,"value":8,"toc":241},"minimark",[9,13,17,28,32,41,49,59,62,66,70,73,77,83,86,96,99,105,109,112,118,124,130,133,146,152,169,178,195,202,207,210,216,219,222,227,235],[10,11,12],"h2",{"id":12},"はじめに",[14,15,16],"p",{},"モバイル版の音声通話アプリを開発するには、サーバー構築、API作成、各端末への接続設定といった色々な手段や時間を費やします。幸いにも、SkywayやTwilioなどのフレームワークのサポートを受けることができます。まず、Skywayについてお話しします。Twilloについて後述させていただきます。",[14,18,19,20,27],{},"概して言えば、Skywayは、ビデオ通話機能や音声通話機能をかんたんにアプリに導入できるマルチプラットフォームのSDKであり、フルマネージドなAPIサービスです。詳細につきましては",[21,22,26],"a",{"href":23,"rel":24},"https:\u002F\u002Fwebrtc.ecl.ntt.com\u002Fen\u002F",[25],"nofollow","Skyway SDK","のホームページにてご参照ください。 この記事では、Skyway SDKを使ったiOS版通話アプリの開発を紹介させていただきます。",[10,29,31],{"id":30},"skywayホームページでのapiキー作成","SkywayホームページでのAPIキー作成",[14,33,34,35,40],{},"登録してから",[21,36,39],{"href":37,"rel":38},"https:\u002F\u002Fconsole-webrtc-free.ecl.ntt.com\u002Fusers\u002Flogin",[25],"Skywayコンソール","にログインします。",[14,42,43,48],{},[21,44,47],{"href":45,"rel":46},"https:\u002F\u002Fconsole-webrtc-free.ecl.ntt.com\u002F",[25],"コンソール","のページで、「DemoSkywayApp」という新しいアプリケーションを作成します。",[50,51],"img",{"className":52,"alt":56,"src":57,"style":58},[53,54,55],"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%;",[14,60,61],{},"作成後、iOSで使用できるようにAPIキーとドメインをある場所に保存しておいてください。",[50,63],{"className":64,"alt":56,"src":65,"style":58},[53,54,55],"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,67,69],{"id":68},"iosプロジェクト作成","iOSプロジェクト作成",[14,71,72],{},"「DemoChatApp」という新しいプロジェクトを作成します。",[50,74],{"className":75,"alt":56,"src":76,"style":58},[53,54,55],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16104243\u002Fcreate_ios_prj-1024x426.png",[14,78,79],{},[80,81,82],"strong",{},"CocoaPodsの構成",[14,84,85],{},"PodFileファイルを作成し、プロジェクトのルートディレクトリに配置して、次のコードをそのファイルに入れます。",[87,88,93],"pre",{"className":89,"code":91,"language":92},[90],"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",[94,95,91],"code",{"__ignoreMap":56},[14,97,98],{},"次に、ターミナルアプリを開き、プロジェクトのルートディレクトリで次のコマンドを実行することでSkywayをインストールします。",[87,100,103],{"className":101,"code":102,"language":92},[90],"pod install\n",[94,104,102],{"__ignoreMap":56},[50,106],{"className":107,"alt":56,"src":108,"style":58},[53,54,55],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16105409\u002Fpod_install-1024x239.png",[14,110,111],{},"アプリ側は音声通話のため端末のマイクを使用するので、端末のマイクにアクセスする権限をinfo.plistファイルに追加する必要があります。",[87,113,116],{"className":114,"code":115,"language":92},[90],"NSMicrophoneUsageDescription\nThis sample uses a mic device for voice chatap\n",[94,117,115],{"__ignoreMap":56},[14,119,120,123],{},[80,121,122],{},"Skyway導入"," まず、PeerServerへの接続設定、ルームへの参加・退出などを処理するコントローラーを作成します。",[87,125,128],{"className":126,"code":127,"language":92},[90],"import UIKit\nimport SkyWay\nimport MBProgressHUD\n\nlet SKYWAY_APIKEY = \"\"\u002F\u002FSkywayコンソールページから取得したAPIキー\nlet SKYWAY_DOMAIN = \"\"\u002F\u002FSkywayコンソールページから取得したドメイン\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\u002FPeerServerへの接続の起動\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\u002FPeerServerへの接続が設定された時のイベント処理\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\u002FPeerServerへの接続がキャンセルされ、繋がらなくなった時のイベント処理\n        currentPeer?.on(.PEER_EVENT_CLOSE, callback: { (obj) in\n            print(\"PEER_EVENT_CLOSE \\(obj!)\")\n            \u002F\u002F端末からPeerServerへのデータストリームのキャンセル\n            self.destroyLocalStream()\n            handler?(false)\n        })\n        \n        \u002F\u002Fユーザーによる取消などの何らかの理由でPeerServerへの接続が切断された時のイベント処理\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\u002F通信回線問題などが原因で、PeerServerへの接続が出来なくなった時のイベント処理\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\u002F特定ルームへの参加\n    func joinRoom(roomName:String, handler:@escaping RoomStatusUpdatedHandler) {\n        \u002F\u002FまだPeerServerに接続していない場合は、事前に接続しておいてください。\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\u002Fルーム構成\n        let option = SKWRoomOption()\n        option.mode = .ROOM_MODE_MESH\n        option.stream = localStream\n        \n        \u002F\u002Fルーム初期化 \n        currentRoom = currentPeer?.joinRoom(withName: roomName, options: option) as? SKWMeshRoom\n        \u002F\u002Fイベント処理\n        \n        \u002F\u002Fルームに正常に参加する時のイベント処理\n        currentRoom?.on(.ROOM_EVENT_OPEN, callback: { (obj) in\n            print(\"room \\(roomName) opened\")\n            handler(.Opened)\n        })\n        \n        \u002F\u002Fルームを出る時のイベント処理\n        currentRoom?.on(.ROOM_EVENT_CLOSE, callback: { (obj) in\n            self.currentRoom?.offAll()\n            self.currentRoom = nil\n            handler(.Closed)\n        })\n                \n        \u002F\u002F他者が追加でルームに参加する時のイベント処理\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\u002F他者がルームを出る時のイベント処理\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        \u002F\u002Fルームへの接続が設定できない時のイベント処理\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\u002Fこのサンプルではビデオ通話機能を使わず音声通話機能のみ使うので、videoFlagをfalseとします。\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",[94,129,127],{"__ignoreMap":56},[14,131,132],{},"以下のように主な関数が三つあります。",[134,135,136,140,143],"ul",{},[137,138,139],"li",{},"connectToPeerServer()",[137,141,142],{},"joinRoom()",[137,144,145],{},"leaveRoom()",[14,147,148,151],{},[80,149,150],{},"connectToPeerServer","() はSkywayのPeerServerへの接続を設定できるように最初に呼び出す必要があります。返却された値はkZoHgbFfTtgBeQhpのような文字列が自分のpeerIdです。 この関数では、次のイベント処理を行う必要があります。",[134,153,154,160,163,166],{},[137,155,156,157],{},"PEER_EVENT_OPEN：PeerServerへの接続に成功した時、データ（音声）を送信するようにPeerServerへのストリーム接続を生成します。この記事では、音声通話のみに焦点を当てるため、videoFlagを無効にしておいてください。",[80,158,159],{},"（デフォルトはYES）",[137,161,162],{},"PEER_EVENT_CLOSE：繋がらなくなった時、ストリームへの接続を切断し、またルームに接続している場合、ルームへの接続も切断します。",[137,164,165],{},"PEER_EVENT_DISCONNECTED：ユーザーが接続を失った場合、上記のイベントと同じ操作を行います。",[137,167,168],{},"PEER_EVENT_ERROR：接続にエラーがあった場合、上記のイベントと同じ操作を行います。",[14,170,171,174,175,177],{},[80,172,173],{},"joinRoom","()はルームに参加するときにルーム名をパラメーターとして呼び出されます。なお、ルーム名は任意に命名できますが、者同士しかルームに参加できないように、ユニークな名前を選択した方がいいです。 これはサーバー側で、ユニークな名前を生成するように処理した方がいいです。 また、",[80,176,150],{},"（）関数と同様に、次のイベント処理を行う必要があります。",[134,179,180,183,186,189,192],{},[137,181,182],{},"ROOM_EVENT_OPEN：ルームに参加する時",[137,184,185],{},"ROOM_EVENT_CLOSE：ルームを出る時",[137,187,188],{},"ROOM_EVENT_PEER_JOIN：他者がルームに参加する時",[137,190,191],{},"ROOM_EVENT_PEER_LEAVE：他者がルームを出る時",[137,193,194],{},"ROOM_EVENT_ERROR：ネットワークまたはタイムアウト等でルームへの接続にエラーが発生する時",[14,196,197,198,201],{},"あと、以下のような簡単な画面の",[80,199,200],{},"CallViewController","ビューコントローラーを作成します。",[50,203],{"className":204,"alt":56,"src":205,"style":206},[53,54,55],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F07\u002F16165958\u002FIMG_9227-576x1024.png","width: 50%;",[14,208,209],{},"CallViewController.swiftファイルに、次のコードを入れます。",[87,211,214],{"className":212,"code":213,"language":92},[90],"import UIKit\nimport SkyWay\n\nclass CallViewController: UIViewController {\n    \n    @IBOutlet weak var joinButton: UIButton!\n    @IBOutlet weak var roomTxt: UITextField!\n        \n    \u002F\u002Fルームのステータスが変わったら、ボタンのタイトルが更新されます。\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    \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",[94,215,213],{"__ignoreMap":56},[14,217,218],{},"ユーザーが参加したいルームの名前を入力するためのテキストフィールドと、ルームに参加または退室するためのボタンがあります。",[14,220,221],{},"こうすることで２台のiOS端末で実行して同じルームに参加して動作を確認することができます。 尚、このアプリはシミュレーターに対応していません。",[14,223,224],{},[80,225,226],{},"参照元",[14,228,229,234],{},[21,230,233],{"href":231,"rel":232},"https:\u002F\u002Fgithub.com\u002Fskyway\u002Fskyway-ios-sdk",[25],"Skyway iOS SDK"," ",[14,236,237],{},[21,238,240],{"href":23,"rel":239},[25],"Skyway Homepage",{"title":56,"searchDepth":242,"depth":242,"links":243},2,[244,245,246],{"id":12,"depth":242,"text":12},{"id":30,"depth":242,"text":31},{"id":68,"depth":242,"text":69},[248,249],"tech talk","mobile","Briswell Vietnam Co Ltd","2020-08-10","md",{},true,"\u002Fja\u002Fnews\u002Fvideochat-skyway",null,{"title":5,"description":16},"ja\u002Fnews\u002Fvideochat-skyway","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2020\u002F08\u002F10091008\u002Fskyway-log.png","Xz91HsZYqxWiaAr2c0WxfZCGYJXIs972nDVfUa5Xkp0",1785320533882]