🚀 クイックスタート

SkyWay のメディア通信を体験できるシンプルなサンプルアプリケーションを作成します。

本チュートリアルでは SFU Room ライブラリを使用します。

ここでは、iPhone のマイク音源とカメラ映像を SFU サーバーに送信し、それぞれを自分が受信して音声を再生と映像の描画するというアプリケーションを作ります。

このクイックスタートの実行には iPhone 実機が必要です。また、 SkyWay Auth Token の生成に npm を利用します。

完成品は https://github.com/skyway/ios-sdk/tree/main/Tutorial にあります。

開発環境

  • Xcode 14.1
  • iOS 13

アプリケーション ID とシークレットキーの取得

※SkyWay への登録がまだの方はこちらから

SkyWay コンソールへログインし、以下の 3 つを行います。

  1. 「アプリケーションを作成」ボタンを押す

Peer

  1. アプリケーション名を入力して作成ボタンを押す

  2. アプリケーション一覧からアプリケーション ID とシークレットキーをコピーする

SkyWay Auth Token の作成

SkyWay Auth Token とは、SkyWay を利用するための JWT 形式のトークンです。

トークンごとに権限を細かく設定することでき、例えば channel ごとの入室を特定ユーザーに制限する、といったことができます。

(参考: 認証・認可)

SkyWay Auth Token はサーバーサイドでの生成になるので、iOS SDK にはトークンの生成機能はございません。ここでは JavaScript SDK で配布している Token ライブラリを利用し、サーバーから認可された後トークンを取得してきたとしましょう。

npm がインストールされた環境でライブラリをインストールします。

$ mkdir skyway_token && cd skyway_token $ npm i @skyway-sdk/token

次に token.js を作成します。

$ touch token.js

ファイルを開き以下のコードをペーストします。

先ほど作成したアプリケーション ID とシークレットキーをスコープの app.idencode 引数にペーストしてください。

const { SkyWayAuthToken, uuidV4 } = require("@skyway-sdk/token"); const token = new SkyWayAuthToken({ jti: uuidV4(), iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, scope: { app: { id: "ここにアプリケーションIDをペーストしてください", turn: true, actions: ["read"], channels: [ { id: "*", name: "*", actions: ["write"], members: [ { id: "*", name: "*", actions: ["write"], publication: { actions: ["write"], }, subscription: { actions: ["write"], }, }, ], sfuBots: [ { actions: ["write"], forwardings: [ { actions: ["write"], }, ], }, ], }, ], }, }, }).encode("ここにシークレットキーをペーストしてください"); console.log(token);

node で token.js を実行すると SkyWay Auth Token が生成されます。このトークンは後程使うのでコピーしてください。

$ node token.js eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxOTA1MzU5Yi0xOGY3LTRhOWMtYmU4Zi1kMTgxMTQ0OTY1MjMiLCJpYXQiOjE2NzQxOTg3MDQsImV4cCI6MTY3NDI4NTEwNCwic2NvcGUiOnsiYXBwIjp7ImlkIjoi44GT44GT44Gr44Ki44OX44Oq44Kx44O844K344On44OzSUTjgpLjg5rjg7zjgrnjg4jjgZfjgabjgY_jgaDjgZXjgYQiLCJ0dXJuIjp0cnVlLCJhY3Rpb25zIjpbInJlYWQiXSwiY2hhbm5lbHMiOlt7ImlkIjoiKiIsIm5hbWUiOiIqIiwiYWN0aW9ucyI6WyJ3cml0ZSJdLCJtZW1iZXJzIjpbeyJpZCI6IioiLCJuYW1lIjoiKiIsImFjdGlvbnMiOlsid3JpdGUiXSwicHVibGljYXRpb24iOnsiYWN0aW9ucyI6WyJ3cml0ZSJdfSwic3Vic2NyaXB0aW9uIjp7ImFjdGlvbnMiOlsid3JpdGUiXX19XSwic2Z1Qm90cyI6W3siYWN0aW9ucyI6WyJ3cml0ZSJdLCJmb3J3YXJkaW5ncyI6W3siYWN0aW9ucyI6WyJ3cml0ZSJdfV19XX1dfX19.qmLpoOjou0S5JwxAiaBvH0KaGzZqN4-0t1xq708_b3M

なお、上記の例ではトークンの有効期限は生成から1日間です。

XcodeProjectの作成・フレームワークの設定

新規で Xcodeproject を作成してください。

作成時、Interfaceは Storyboard を選択してください。

SelectStoryboard

パーミッションの設定

info.plistPrivacy - Microphone Usage DescriptionPrivacy - Camera Usage Description を追加して、value にはユーザーに利用許可を確認するプロンプトのメッセージを登録してください。

ここでは、マイクを利用しますカメラを利用します と登録します。

InfoPlist

Room SDKのダウンロード

今回は Swift Package Manager にてダウンロードします。

Xcode から Project を選択し、 Package Dependencies を選択します。

左下 + ボタンからパッケージ検索のモーダルを表示させ、右上の URL 検索ボックスに https://github.com/skyway/ios-sdk.git と入力します。

Package Product SkyWayRoom がチェックされていることを確認し、 Add Package を押下します。

映像ビューのセットアップ

今回は、カメラからキャプチャしている Local の View (プレビュー)と SFU サーバーから受信した Remote の View を描画します。

Storyboard を使って解説します。

まず、View コ ンポーネントを作成し、ViewController 直下の View に追加します。

StoryBoardView

View の名前を Local View にリネームします。

StoryBoardLocalView

この View の Custom Class の設定で Class 名を SKWCameraPreviewView に設定します。

この View を複製して、Remote View にリネームします。

StoryBoardRemoteView

この View の Custom Class の設定で Class 名を SKWVideoView に設定します。

適宜 Auto Layout を利用して View の constraint を設定します。

上側を LocalView, 下側を RemoteView に配置します。

StoryBoardConstraints

IBOutlet を利用して Local ViewRemote View をそれぞれ変数宣言します。この時、Type は SKW を消した CameraPreviewViewVideoView 宣言してください。

デフォルトでは`SKW`プレフィックスがついているのでご注意ください。

IBOutletLocalView

IBOutletRemoteView

Contextのセットアップ

ここでは、簡易的に Initial ViewController の viewDidLoad に SkyWay のロジックを記述していきます。

class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Impl here } }

import SkyWayRoom でフレームワークを import してください。

import UIKit import SkyWayRoom class ViewController: UIViewController {

後ほど利用する room と先ほど生成したトークンをメンバー変数で宣言します。

トークンは前セクションで生成した JWT に変更してください。

Context.setup(withToken:options:completion:) で SkyWay のセットアップを行います。先ほど生成した JWT を引数に入れてください。 completion にて失敗した場合は Error が返ります。ここでは async/await 形式で記述します。

override func viewDidLoad() { super.viewDidLoad() Task { let token: String = "トークンを入力" // SkyWayのセットアップ try? await Context.setup(withToken: token, options: nil)

Roomの作成

SkyWay のセットアップが完了したら Room を作成します。

Room には P2P とメディアサーバーを介する SFU がありますが、今回は SFU を利用します。

SFURoom.create(with:completion:) で Room を作成できます。

let roomInit: Room.InitOptions = .init() guard let room: SFURoom = try? await .create(with: roomInit) else { print("[Tutorial] Creating room failed.") return }

Roomへの参加

SFURoom.join(with:completion:) でルームに参加し、メンバーを作成します。

let memberInit: Room.MemberInitOptions = .init() memberInit.name = "Alice" // Memberに名前を付けることができます guard let member = try? await room.join(with: memberInit) else { print("[Tutorial] Join failed.") return }

マイク音源のAudioStreamの作成とRoomへのPublish

マイクを音声入力として Stream を作成します。

Publish をするための LocalStream を作成するためには、Source が必要です。

MicrophoneAudioSource のイニシャライザから Source を作成し、createStream() で LocalStream を作成します。

LocalSFUMember.publish(_:options:completion:) で Room に Publish します。

// AudioStreamの作成 let auidoSource: MicrophoneAudioSource = .init() let audioStream = auidoSource.createStream() guard let audioPublication = try? await member.publish(audioStream, options: nil) else { print("[Tutorial] Publishing failed.") return }

AudioStreamのSubscribe

先ほど Publish した Stream を LocalSFUMember.subscribe(publicationId:options:completion:) を利用して、Subscribe して Stream を受け取ります。

PublicationID は RoomPublicationid で取得できます。

※P2PRoomでは、自分のPublishしたStreamをSubscribeできませんのでご注意ください
// Audioの場合、subscribeした時から音声が流れます guard let _ = try? await member.subscribe(publicationId: audioPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing audio stream successfully.")

カメラの設定とプレビューの描画

カメラ映像ソースの場合はカメラの設定とキャプチャ操作が必要です。

CameraVideoSource.supportedCameras() で SkyWay がサポートしているカメラの一覧を取得できます。

今回は前面カメラデバイスを取得します。

// Cameraの設定 guard let camera = CameraVideoSource.supportedCameras().first(where: { $0.position == .front }) else { print("Supported cameras is not found."); return }

CameraVideoSource.shared().startCapturing(with:options:completion:) でキャプチャーを開始します。

// キャプチャーの開始 try! await CameraVideoSource.shared().startCapturing(with: camera, options: nil)

確認用に CameraVideoSource.shared().attach(localView) でキャプチャリングをしているカメラ映像を描画します。

// Previewの描画 CameraVideoSource.shared().attach(localView)

カメラ映像ソースのVideoStreamの作成とRoomへのPublish

Stream は CameraVideoSource.shared().createStream() で作成できます。

AudioStream 同様、publish(_:options:completion:) で Publish を行います。

// VideoStreamの作成 let localVideoStream = CameraVideoSource.shared().createStream() guard let videoPublication = try? await member.publish(localVideoStream, options: nil) else { print("[Tutorial] Publishing failed.") return }

VideoStreamのSubscribe

AudioStream 同様、subscribe(publicationId:completion:) を利用して、Subscribe して Stream を受け取ります。

guard let videoSubscription = try? await member.subscribe(publicationId: videoPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing video stream successfully.")

RemoteViewの描画

Subscription の stream を取得し、RemoteVideoStream にキャストします。

attach(_)remoteView を指定することで、SFU から受信した映像を描画できます。

let remoteVideoStream = videoSubscription.stream as! RemoteVideoStream remoteVideoStream.attach(remoteView)

Tutorial完成コード

import UIKit import SkyWayRoom class ViewController: UIViewController { @IBOutlet weak var localView: CameraPreviewView! @IBOutlet weak var remoteView: VideoView! override func viewDidLoad() { super.viewDidLoad() Task { let token: String = "トークンを入力" // SkyWayのセットアップ let contextOpt: ContextOptions = .init() contextOpt.logLevel = .trace try? await Context.setup(withToken: token, options: contextOpt) let roomInit: Room.InitOptions = .init() guard let room: SFURoom = try? await .create(with: roomInit) else { print("[Tutorial] Creating room failed.") return } let memberInit: Room.MemberInitOptions = .init() memberInit.name = "Alice" // Memberに名前を付けることができます guard let member = try? await room.join(with: memberInit) else { print("[Tutorial] Join failed.") return } // AudioStreamの作成 let auidoSource: MicrophoneAudioSource = .init() let audioStream = auidoSource.createStream() guard let audioPublication = try? await member.publish(audioStream, options: nil) else { print("[Tutorial] Publishing failed.") return } // Audioの場合、subscribeした時から音声が流れます guard let _ = try? await member.subscribe(publicationId: audioPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing audio stream successfully.") // Cameraの設定 guard let camera = CameraVideoSource.supportedCameras().first(where: { $0.position == .front }) else { print("Supported cameras is not found."); return } // キャプチャーの開始 try! await CameraVideoSource.shared().startCapturing(with: camera, options: nil) // Previewの描画 CameraVideoSource.shared().attach(localView) // VideoStreamの作成 let localVideoStream = CameraVideoSource.shared().createStream() guard let videoPublication = try? await member.publish(localVideoStream, options: nil) else { print("[Tutorial] Publishing failed.") return } guard let videoSubscription = try? await member.subscribe(publicationId: videoPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing video stream successfully.") let remoteVideoStream = videoSubscription.stream as! RemoteVideoStream remoteVideoStream.attach(remoteView) } } }

実行

iPhone(実機)にて Run します。

iPhone Simulatorではカメラが利用できないので実機にて実行してください。

iPhone 実機に声をかけたり音声を鳴らしてみてください。音声が出力され、LocalView と RemoteView に映像が描画されれば成功です。

次のステップ

今回は SFU サーバーを介して自分が Publish したメディアを Subscribe するというシンプルなものでした。

次は、他のクライアントと映像・音声・データをやりとりしてみましょう。他のクライアントと疎通できるサンプルアプリケーションを用意しています。

サンプルコード

また、開発の前に開発ドキュメントもご一読ください。

iOS SDKの開発ドキュメント