クイックスタート

この章では、 Sora iOS SDK を使ってシンプルな iOS アプリケーションを作成するまでの流れを紹介します。 この iOS アプリケーション開発を通じて、次の Sora iOS SDK の使い方を学べます。

  • メディアデータ (映像と音声) の送信と受信
  • 送受信する映像の描画
  • 端末のデバイス (カメラとマイク) の使用
  • カメラの操作

ソースコードは sora-ios-sdk-quickstart リポジトリで配布しています。

用意するもの

バージョンなどの詳細な条件は システム条件 を参照してください。

  • iOS 端末 (Sora iOS SDK はシミュレーターに対応していません)
  • Mac OS X と Xcode
  • Carthage または CocoaPods
  • WebRTC に対応している Web ブラウザ (Chrome や Firefox など)
  • Web カメラとマイク
  • Sora

サーバーについては、ここでは次の条件での運用を仮定します。 詳しくは Sora のドキュメントを参照してください。

  • サーバーの IP アドレス: "192.168.0.2"
  • シグナリングのポート番号: 5000
  • デモ機能: 有効
  • HTTP を使用
  • WebSocket の接続に ws を使用
  • メディアチャネル ID: "ios-quickstart"

サンプルアプリケーションをビルドする

コードを書く前に、サンプルアプリケーション SoraQuickStart をビルドして実行してみましょう。

重要

サンプルアプリケーションは Carthage のパスに /usr/local/bin/carthage を仮定しています。 Carthage が異なるパスにインストールされている場合はシンボリックリンクを用意するか、再インストールを推奨します。

ソースコードをダウンロードする

サンプルアプリケーションのソースコードは sora-ios-sdk-quickstart リポジトリで配布しています。 develop ブランチをクローンもしくはダウンロードしてください。

ライブラリをダウンロードする

ビルドに必要なライブラリをダウンロードします。 Carthage か CocoaPods を利用します。

Carthage を利用する

Cartfile のあるトップレベルのディレクトリで次のコマンドを実行してください。 ライブラリのダウンロードが完了すると、 Carthage ディレクトリが生成されます。

$ carthage update --platform iOS
*** Fetching sora-ios-sdk
...

CocoaPods を利用する

CocoaPods を利用してダウンロードするには、 Sora iOS SDK の Spec リポジトリをローカルの環境に登録する必要があります。 セットアップの CocoaPods を利用する を参照してください。

Spec リポジトリを登録したら、 pod install コマンドを実行してダウンロードします。 ダウンロード後に SoraQuickStart.xcworkspace が生成されるので、以降はこのファイルを開いてプロジェクトの開発を行います。

接続情報を設定する

SoraQuickStart の Xcode プロジェクトを開き、接続するサーバーのシグナリング URL とチャネル ID を ViewController.swift ファイルで指定します。

ViewController.swift:

// 接続するサーバーのシグナリング URL
let soraURL = URL(string: "ws://192.168.0.2:5000/signaling")!

// チャネル ID
let soraChannelId = "ios-quickstart"

ビルドして実行する

iOS 端末をマシンに接続します (Sora iOS SDK はシミュレーターに対応していません。実機を利用してください) 。ビルドターゲットのアーキテクチャに接続した iOS 端末を選択してビルド・実行します (メニュー "Product" > "Run") 。

ビルド対象アーキテクチャの選択:

_images/build_arch.png

サーバーに接続する

アプリケーションが起動したら、 Connect ボタンをタップするとサーバーに接続します。 接続に成功するとカメラとマイクの使用許可を求めるダイアログが表示されます。 許可後、上下のビューにカメラの映像が表示されれば接続成功です。

接続できなかった場合はサーバーの URL やネットワークの状態を確認してください。 質問と答え も参照してください。

サンプルアプリケーションを実装する

アプリケーションの仕様

  • サーバーに接続します。
  • カメラの映像をサーバーに送信します。送信する映像を画面に描画します。
  • サーバーから映像を受信します。受信した映像を画面に描画します。
  • カメラの前後の位置を切り替えます。

Xcode プロジェクトを作成する

最初に iOS アプリケーションを開発するプロジェクトを用意します。 Xcode でプロジェクトを作成し、 "Single View Application" を選択します。 "Product Name" は "SoraQuickStart" 、 "Language" は "Swift" を選択します。

_images/create_project.png

注釈

sora-ios-sdk-quickstart リポジトリのサンプルアプリケーションでは "Display Name" を "QuickStart" に変更していますが、この変更は必須ではありません。

_images/display_name.png

Sora iOS SDK 用の設定を行う

Xcode プロジェクトのセットアップ に従って Sora iOS SDK の設定をしてください。

Sora iOS SDK API の概要

Sora iOS SDK は次の要素を中心に構成されます。

  • メディアチャネル (MediaChannel) は WebRTC のメディアチャネルを表します。 Sora iOS SDK ではメディアチャネルの ID を指定してサーバーに接続します。
  • ロール (Role) はメディアチャネルが扱う映像の送受信の方向を表します。ロールの種類を次に示します。
    • パブリッシャー (Role.publisher) は端末のカメラの映像をサーバーに送信します。
    • サブスクライバー (Role.subscriber) はサーバーから映像を受信します。
    • グループ (パブリッシャー) (Role.group) はマルチストリームの接続です。複数人の間で映像の送受信を行います。このアプリケーションでは扱いません。
    • グループ (サブスクライバー) (Role.groupSub) はマルチストリームの接続です。複数人の間で映像の送受信を行いますが、このロールを指定したクライアントは受信のみを行います。このアプリケーションでは扱いません。
  • メディアストリーム (MediaStream) は映像のストリームを表すオブジェクトです。グループ (マルチストリーム) では同チャネルへの接続数と同じ数のストリームが使用されます。グループ以外 (シングルストリーム) のパブリッシャーとサブスクライバーではストリームは 1 つです。
  • 映像ビュー (VideoView) はメディアストリームの映像と音声を描画するビューオブジェクトです。いずれのロールの映像も描画できます。

受信した映像を描画する画面を作成する

サーバーからの映像の受信から実装を始めます。 最終的な画面は次のようになります。 背景と区別しやすいように映像ビューの背景を黒にしています。

_images/downstream_view.png
  1. Main.storyboad を開いて、 View Controller に View コンポーネントを配置します。端末の種類はアプリケーションを動かす任意の端末に変更してください。実装を簡略化するため、ここでは制約を指定しません。

  2. インスペクタの Custom Class の "Class" に "VideoView" を、 "Module" に "Sora" を指定します。 Sora iOS SDK では送受信の方向に関わらず映像の描画を VideoView で行います。

    _images/videoview_customclass.png
  3. View コンポーネントの下に接続を行うボタンと接続解除を行うボタンを配置します。ボタン名をそれぞれ "Connect", "Disconnect" に変更します。

  4. 以上のコンポーネントのアウトレットを定義し、 Interface Builder でコンポーネントをアウトレットに接続します。

    import UIKit
    import Sora
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var subscriberViedoView: VideoView!
        @IBOutlet weak var connectButton: UIButton!
        @IBOutlet weak var disconnectButton: UIButton!
    
        ...
    }
    
  5. ボタンを押したときに接続と接続解除を行うメソッドを View Controller に定義します。

    // 接続
    @IBAction func connect(_ sender: AnyObject) {
    
    }
    
    // 接続解除
    @IBAction func disconnect(_ sender: AnyObject) {
    
    }
    
  6. 接続インスペクタを表示し、 "Send Events" > "Touch Up Inside" に 5. のメソッドを接続します。この画像では "connect" のみですが、 "disconnect" も同様に設定します。

    _images/set_action_event.png

サーバーに接続して映像を受信する

ソースコード

サーバーに接続するには次の情報が必要になります。

  • サーバーのシグナリング用 URL
  • チャネル ID
  • ロール (映像の送受信の方向)

サーバーが配信する映像はチャネル ID に紐付けられ、受信側はそのチャネル ID にアクセスして映像を受信します。

接続と接続解除の処理を次のように実装します。

var subscriber: MediaChannel!

override func viewDidLoad() {
    super.viewDidLoad()
    connectButton.isEnabled = true
    disconnectButton.isEnabled = false

}

@IBAction func connect(_ sender: AnyObject) {
    connectButton.isEnabled = false
    disconnectButton.isEnabled = false

    // シグナリング URL とチャネル ID を指定する
    let url = URL(string: soraURL)!
    let subConfig = Configuration(url: url,
                                  channelId: soraChannelId,
                                  role: .sublisher)

    // 接続する
    Sora.shared.connect(configuration: subConfig) { pub, error in
        // 接続に失敗するとエラーが渡される。
        // 接続に成功すると error は nil
        if let error = error {
            print(error.localizedDescription)
            DispatchQueue.main.async {
                self.connectButton.isEnabled = true
            }
            return
        }
        DispatchQueue.main.async {
            self.disconnectButton.isEnabled = true
        }

        // 映像を描画するビューをストリームにセットする
        self.subscriber = sub
        self.subscriber.mainStream!.videoRenderer = self.subscriberVideoView
    }
}

@IBAction func disconnect(_ sender: AnyObject) {
    subscriber.disconnect(error: nil)
    self.connectButton.isEnabled = true
    self.disconnectButton.isEnabled = false
}

まずは試してみましょう。 サーバーを起動し、 Web ブラウザでデモ用の URL (http://192.168.0.2:5000/up.html?channel_id=ios-quickstart) にアクセスします。 同チャネル ID に配信してから iOS アプリケーションを起動し、 "Connect" ボタンをタップすると映像が表示されるはずです。

サーバーに接続する

サーバーへの接続は次の手順で行います。

  1. Configuration を生成する。
  2. Sora オブジェクトの connect(configuration:webRTCConfiguration:handler:) を呼ぶ。

Configuration はサーバーへの接続情報などの設定を保持するオブジェクトです。 サーバーの URL 、チャネル ID 、ロールをセットします。 ロールは送受信の方向を表し、映像を受信するサブスクライバーは .subscriber で指定します。

次に Configuration を引数として Sora オブジェクトの connect(configuration:webRTCConfiguration:handler:) を呼びます (Sora オブジェクトは Sora.shared プロパティでシングルトンを取得できます) 。 このメソッドはサーバーへの接続を行い、接続の成否に関わらず handler: に指定されたブロックを実行します。 接続が成功すると MediaChannel がブロックの引数として渡されます。 MediaChannel は接続後の操作を行うためのオブジェクトです。

connect(configuration:webRTCConfiguration:handler:) のメソッドの型と引数は次の通りです。

func connect(configuration: Configuration,
             webRTCConfiguration: WebRTCConfiguration = WebRTCConfiguration(),
             handler: (_ mediaChannel: MediaChannel?, _ error: Error?) -> Void)
  • configuration: 接続情報です。シグナリング URL とチャネル ID を指定します。
  • webRTCConfiguration: WebRTC に関する設定です (省略可) 。このアプリケーションでは扱いません。
  • handler: 接続試行後に実行するブロックです。ブロックの型は (MediaChannel?, Error?) -> Void です。ブロックの引数は次の通りです。
    • mediaChannel: 接続に成功するとメディアチャネルが渡されます。失敗時は nil です。
    • error: 接続に失敗するとエラーが渡されます。成功時は nil です。

MediaChannel は映像データを扱うストリームを複数保持します。 映像はストリームを通じて Web ブラウザやサーバーと送受信されます。

ロールとストリーム

ストリームは送信か受信のいずれかを担当し、ロールによって扱う数が異なります。 パブリッシャーとサブスクライバーのストリームはそれぞれ 1 つです (シングルストリーム) 。 パブリッシャーのストリームは映像の送信を、サブスクライバーのストリームは映像の受信を行います。 つまり、シングルストリームでは 1 回の接続につき送信か受信かのいずれか一方しか行えません。 1 台の端末で映像の送受信を行う場合は、送信と受信で 2 回の接続が必要です。 マルチストリームでは 1 回の接続で複数のストリームを扱えますが、このアプリケーションでは扱いません。

受信した映像を描画する

接続に成功したら、 View Controller に配置した映像ビュー subscriberVideoView を主ストリーム mainStreamvideoRenderer プロパティにセットします (主ストリームは複数のストリームのうちの最初のストリームで、 streams[0] と同等です。前述の通りシングルストリームで扱われるストリームは 1 つのみですので、主ストリームのみの操作で問題ありません) 。 videoRenderer プロパティにセットした映像ビューは、ストリームに流された映像を描画します。 サブスクライバーではサーバーから受信した映像が、パブリッシャーではサーバーに送信する映像が描画されます。

送信する映像を描画する画面を作成する

次に映像の送信を実装してみましょう。 iOS 端末のカメラとマイクを使い、映像と音声をサーバーに送信します。 加えて前節で実装した受信機能を使い、送信した映像をサーバーから受信して描画します。

最終的な画面は次の通りです。

_images/upstream_view.png
  1. 新しいボタンと新しい View コンポーネントを受信用のビューの上に配置します。 ボタンの Title を "Switch Camera Position" に変更します。 このボタンはカメラの前面と背面を切り替える目的に使います。

  2. View コンポーネントの Custom Class を受信用のビューと同様にして設定します。

  3. ViewController クラスに 1. のビューを保持するアウトレット publisherVideoView を追加し、 Interface Builder で 1 . のビューを接続します。

    @IBOutlet weak var publisherVideoView: VideoView!
    
  4. カメラの前面と背面を切り替えるアクションを用意します。 次のメソッドを定義し、接続インスペクタで "Touch Up Inside" にメソッドを接続します。

    @IBAction func switchCameraPosition(_ sender: AnyObject) {
    }
    
  5. サブスクライバーの接続を管理する MediaChannel のプロパティを追加します。

    var subscriber: MediaChannel!
    

映像を送信する

映像を送信するには、ロールにパブリッシャー .publisher を指定して接続します。 グループ .group 以外のロールでは、 1 つの接続につき送受信の一方しか扱えません。 送信と受信を同時に行うためには、パブリッシャーとサブスクライバーの 2 つの接続を行う必要があります。

connectdisconnect メソッドに送信処理を追加した最終的なコードを次に示します。 まずパブリッシャーで接続し、接続が成功したらサブスクライバーを接続します。

@IBAction func connect(_ sender: AnyObject) {
    connectButton.isEnabled = false
    disconnectButton.isEnabled = false

    // シグナリング URL とチャネル ID を指定する
    let pubConfig = Configuration(url: soraURL,
                                  channelId: soraChannelId,
                                  role: .publisher)

    // パブリッシャーを接続する
    Sora.shared.connect(configuration: pubConfig) { pub, error in
        // 接続に失敗するとエラーが渡される。
        // 接続に成功すると error は nil
        if let error = error {
            print(error.localizedDescription)
            DispatchQueue.main.async {
                self.connectButton.isEnabled = true
            }
            return
        }

        // サブスクライバーを接続する
        let subConfig = Configuration(url: soraURL,
                                      channelId: soraChannelId,
                                      role: .subscriber)
        Sora.shared.connect(configuration: subConfig) {
            sub, error in
            if let error = error {
                print(error.localizedDescription)
                DispatchQueue.main.async {
                    self.connectButton.isEnabled = true
                }
                return
            }
            DispatchQueue.main.async {
                self.disconnectButton.isEnabled = true
            }

            // 映像を描画するビューをストリームにセットする
            self.publisher = pub
            self.publisher.mainStream!.videoRenderer = self.publisherVideoView
            self.subscriber = sub
            self.subscriber.mainStream!.videoRenderer = self.subscriberVideoView
        }
    }
}

@IBAction func disconnect(_ sender: AnyObject) {
    publisher.disconnect(error: nil)
    subscriber.disconnect(error: nil)
    self.connectButton.isEnabled = true
    self.disconnectButton.isEnabled = false
}

パブリッシャーの接続完了後にサブスクライバーの接続を行うために接続処理がネストしていますが、パブリッシャーの接続方法と接続解除方法はサブスクライバーと同じです。

ビルドできたら実行してみましょう。 今度の操作は端末で完結するので Web ブラウザは不要です。 パブリッシャーをサーバーに接続すると、端末のカメラとマイクが自動的に準備されます (Info.plist の設定をしないとアプリケーションが強制終了しますので注意してください) 。 両方のビューに映像が表示されれば成功です。

カメラとストリーム

カメラとマイクはパブリッシャーの接続時に初期化されます。 Sora iOS SDK では、カメラの映像は CameraVideoCapturer が扱います。 (Info.plist にプライバシーの設定を記述しないとアプリケーションが落ちますので注意してください )。

パブリッシャーのストリームは、 VideoCapturer プロトコルを実装したオブジェクトから映像を取得してサーバーに送信します。 デフォルトの設定では CameraVideoCapturer が取得した映像がストリームに渡されます。

カメラの前面と背面を切り替える

最後にカメラの前面と背面を切り替える処理を、先に定義した switchCameraPosition メソッドに実装します。 デフォルトのカメラの位置は前面です。

@IBAction func switchCameraPosition(_ sender: AnyObject) {
    if disconnectButton.isEnabled {
        CameraVideoCapturer.shared.flip()
    }
}

CameraVideoCapturerflip メソッドでカメラの位置を反転します。

その他のサンプルコード

下記のリポジトリでその他の様々なサンプルコードを公開しています。

https://github.com/shiguredo/sora-ios-sdk-samples

まとめ

以上のアプリケーション開発で触れた Sora iOS SDK のポイントを以下にまとめます。

  • サーバーの接続には「サーバーのシグナリング用 URL 」と「チャネル ID 」が必要。接続に関する設定を Configuration にセットする。
  • サーバーの接続は Sora オブジェクトの connect(configuration:webRTCConfiguration:handler:) を呼ぶ。
  • メディアデータの送受信の方向はロール Role で指定する。パブリッシャー (.publisher) で送信、サブスクライバー (.subscriber) で受信、グループ (.group) でマルチストリームの送受信を行う。
  • メディアデータはストリーム MediaStream を通じて送受信される。 1 つの接続に使われるストリームの数はロールによって異なる。グループ (マルチストリーム) では接続人数と同じ数で、それ以外のロール (シングルストリーム) では 1 つ。
  • 映像の描画は VideoView オブジェクトが行う。 VideoView はストリームの videoRenderer プロパティにセットする。
  • 端末のカメラとマイクはパブリッシャーの接続時に初期化されて使用可能になる。
  • ストリームに送信するカメラの映像は CameraVideoCapturer が扱う。 flip() メソッドでカメラの位置を反転できる。