iOS SDK
概要
クイックスタート
- 開発環境
- アプリケーション ID とシークレットキーの取得
- SkyWay Auth Token の作成
- XcodeProjectの作成・フレームワークの設定
- パーミッションの設定
- SDKのダウンロード
- 映像ビューのセットアップ
- Contextのセットアップ
- Roomの作成
- Roomへの参加
- マイク音源のAudioStreamの作成とRoomへのPublish
- AudioStreamのSubscribe
- カメラの設定とキャプチャの開始
- カメラ映像ソースのVideoStreamの作成とRoomへのPublish
- VideoStreamのSubscribe
- RemoteVideoViewの描画
- View と ViewModel を接続
- Tutorial完成コード
- 実行
- 次のステップ
音声・映像入力ソースと LocalStream の作成方法
解放・破棄処理
Tips
既知の問題
🚀 クイックスタート
SkyWay のメディア通信を体験できるシンプルなサンプルアプリケーションを作成します。
本チュートリアルでは SFU でのメディア通信を使用します。
ここでは、iPhone のマイク音源とカメラ映像を SFU サーバーに送信し、それぞれを自分が受信して音声を再生と映像の描画するというアプリケーションを作ります。
このクイックスタートの実行には iPhone 実機が必要です。
完成品は https://github.com/skyway/ios-sdk/tree/main/Tutorial にあります。
開発環境
- Xcode 26.0.1
- iOS 26.0.1
アプリケーション ID とシークレットキーの取得
※SkyWay への登録がまだの方はこちらから
SkyWay コンソールへログインし、以下の 3 つを行います。
- 「アプリケーションを作成」ボタンを押す

- アプリケーション名を入力して作成ボタンを押す
- アプリケーション一覧からアプリケーション ID とシークレットキーをコピーする
SkyWay Auth Token の作成
SkyWay を利用するためには、初めに JWT(JSON Web Token)を用いて Context を初期化します。
SkyWay Auth Token は本来サーバーサイドで生成するため、 iOS SDK にはトークンの生成機能はございません。
クイックスタートでは、 Dev 環境専用の API である Context.setupForDev(withAppId:secretKey:options:completion:) を用いて初期化するため、 SkyWay Auth Token の作成は省略します。
認証認可について、詳しくはこちらをご覧ください。
XcodeProjectの作成・フレームワークの設定
新規で Xcodeproject を作成してください。
作成時、Interface は SwiftUI を選択してください。
パーミッションの設定
info.plist の Privacy - Microphone Usage Description と Privacy - Camera Usage Description を追加して、value にはユーザーに利用許可を確認するプロンプトのメッセージを登録してください。
ここでは、マイクを利用します と カメラを利用します と登録します。

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 を描画します。
SwiftUI では UIKit の CameraPreviewView と VideoView を UIViewRepresentable でラップして使用します。
CameraPreview.swift を新規作成します。updateUIView 内で CameraVideoSource.shared().attach(_:) を呼ぶことで、カメラ映像のプレビューを描画します。
import SwiftUI
import SkyWayRoom
struct CameraPreview: UIViewRepresentable {
func makeUIView(context: Context) -> CameraPreviewView {
return CameraPreviewView()
}
func updateUIView(_ uiView: CameraPreviewView, context: Context) {
CameraVideoSource.shared().attach(uiView)
}
static func dismantleUIView(_ uiView: CameraPreviewView, coordinator: ()) {
CameraVideoSource.shared().detach(uiView)
}
}次に RemoteVideoView.swift を新規作成します。RemoteVideoStream を受け取り、updateUIView 内でアタッチすることで SFU から受信した映像を描画します。
import SwiftUI
import SkyWayRoom
struct RemoteVideoView: UIViewRepresentable {
let stream: RemoteVideoStream?
func makeUIView(context: Context) -> VideoView {
let view = VideoView()
view.videoContentMode = .scaleAspectFit
return view
}
func updateUIView(_ uiView: VideoView, context: Context) {
stream?.attach(uiView)
}
static func dismantleUIView(_ uiView: VideoView, coordinator: ()) {}
}Contextのセットアップ
RoomViewModel.swift を新規作成し、SkyWay のロジックをまとめます。
@MainActor と ObservableObject を指定し、@Published で Remote の映像ストリームを保持します。
import SkyWayRoom でフレームワークを import してください。
アプリケーション ID とシークレットキーをメンバー変数で宣言します。
Context.setupForDev(withAppId:secretKey:options:completion:) で SkyWay のセットアップを行います。
Context.setupForDev(withAppId:secretKey:options:completion:)は Dev 環境での利用が想定されている API です。 本番環境では、SecretKeyを秘匿するためContext.setup(withToken:options:completion:)をご利用ください。
import Foundation
import SkyWayRoom
@MainActor
final class RoomViewModel: ObservableObject {
@Published var remoteVideoStream: RemoteVideoStream?
func start() async {
let appId = "アプリケーションIDを入力してください"
let secretKey = "シークレットキーを入力してください"
// SkyWayのセットアップ
let contextOpt: ContextOptions = .init()
contextOpt.logLevel = .trace
try? await Context.setupForDev(withAppId: appId, secretKey: secretKey, options: contextOpt)Roomの作成
SkyWay のセットアップが完了したら Room を作成します。
Room.create(with:completion:) で Room を作成できます。
let roomInit: Room.InitOptions = .init()
guard let room: Room = try? await .create(with: roomInit) else {
print("[Tutorial] Creating room failed.")
return
}Roomへの参加
Room.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 を作成します。
通信方式は P2P とメディアサーバーを介する SFU がありますが、今回は SFU を利用します。
通信方式は RoomPublicationOptions の type プロパティで指定できます。
LocalRoomMember.publish(_:options:completion:) で Room に Publish します。
// AudioStreamの作成
let audioSource: MicrophoneAudioSource = .init()
let audioStream = audioSource.createStream()
let audioPublicationOptions: RoomPublicationOptions = .init()
audioPublicationOptions.type = .SFU
guard let audioPublication = try? await member.publish(audioStream, options: audioPublicationOptions) else {
print("[Tutorial] Publishing failed.")
return
}AudioStreamのSubscribe
先ほど Publish した Stream を LocalRoomMember.subscribe(publicationId:options:completion:) を利用して、Subscribe して Stream を受け取ります。
PublicationID は RoomPublication の id で取得できます。
※P2P方式では、自分の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:) でキャプチャーを開始します。
キャプチャが開始されると、CameraPreview が自動的にカメラ映像を描画します。
// キャプチャーの開始
try! await CameraVideoSource.shared().startCapturing(with: camera, options: nil)カメラ映像ソースのVideoStreamの作成とRoomへのPublish
Stream は CameraVideoSource.shared().createStream() で作成できます。
AudioStream 同様、publish(_:options:completion:) で Publish を行います。
// VideoStreamの作成
let localVideoStream = CameraVideoSource.shared().createStream()
let videoPublicationOptions: RoomPublicationOptions = .init()
videoPublicationOptions.type = .SFU
guard let videoPublication = try? await member.publish(localVideoStream, options: videoPublicationOptions) 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.")RemoteVideoViewの描画
Subscription の stream を取得し、RemoteVideoStream にキャストします。
remoteVideoStream プロパティに代入すると ContentView が再描画され、RemoteVideoView に SFU から受信した映像が表示されます。
let remoteVideoStream = videoSubscription.stream as! RemoteVideoStream
self.remoteVideoStream = remoteVideoStreamView と ViewModel を接続
ContentView.swift で RoomViewModel を @StateObject として保持し、.onAppear で start() を呼び出します。
import SwiftUI
struct ContentView: View {
@StateObject private var viewModel = RoomViewModel()
var body: some View {
VStack(spacing: 0) {
CameraPreview()
.frame(maxWidth: .infinity, maxHeight: .infinity)
RemoteVideoView(stream: viewModel.remoteVideoStream)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.onAppear {
Task { await viewModel.start() }
}
}
}Tutorial完成コード
CameraPreview.swift
import SwiftUI
import SkyWayRoom
struct CameraPreview: UIViewRepresentable {
typealias UIViewType = CameraPreviewView
typealias Context = UIViewRepresentableContext<Self>
func makeUIView(context: Context) -> CameraPreviewView {
return CameraPreviewView()
}
func updateUIView(_ uiView: CameraPreviewView, context: Context) {
CameraVideoSource.shared().attach(uiView)
}
static func dismantleUIView(_ uiView: CameraPreviewView, coordinator: ()) {
CameraVideoSource.shared().detach(uiView)
}
}RemoteVideoView.swift
import SwiftUI
import SkyWayRoom
struct RemoteVideoView: UIViewRepresentable {
typealias UIViewType = VideoView
typealias Context = UIViewRepresentableContext<Self>
let stream: RemoteVideoStream?
func makeUIView(context: Context) -> VideoView {
let view = VideoView()
view.videoContentMode = .scaleAspectFit
return view
}
func updateUIView(_ uiView: VideoView, context: Context) {
stream?.attach(uiView)
}
static func dismantleUIView(_ uiView: VideoView, coordinator: ()) {}
}RoomViewModel.swift
import Foundation
import SkyWayRoom
@MainActor
final class RoomViewModel: ObservableObject {
@Published var remoteVideoStream: RemoteVideoStream?
func start() async {
let appId = "アプリケーションIDを入力してください"
let secretKey = "シークレットキーを入力してください"
// SkyWayのセットアップ
let contextOpt: ContextOptions = .init()
contextOpt.logLevel = .trace
try? await Context.setupForDev(withAppId: appId, secretKey: secretKey, options: contextOpt)
let roomInit: Room.InitOptions = .init()
guard let room: Room = 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 audioSource: MicrophoneAudioSource = .init()
let audioStream = audioSource.createStream()
let audioPublicationOptions: RoomPublicationOptions = .init()
audioPublicationOptions.type = .SFU
guard let audioPublication = try? await member.publish(audioStream, options: audioPublicationOptions) 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)
// VideoStreamの作成
let localVideoStream = CameraVideoSource.shared().createStream()
let videoPublicationOptions: RoomPublicationOptions = .init()
videoPublicationOptions.type = .SFU
guard let videoPublication = try? await member.publish(localVideoStream, options: videoPublicationOptions) 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
self.remoteVideoStream = remoteVideoStream
}
}ContentView.swift
import SwiftUI
struct ContentView: View {
@StateObject private var viewModel = RoomViewModel()
var body: some View {
VStack(spacing: 0) {
CameraPreview()
.frame(maxWidth: .infinity, maxHeight: .infinity)
RemoteVideoView(stream: viewModel.remoteVideoStream)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.onAppear {
Task { await viewModel.start() }
}
}
}実行
iPhone(実機)にて Run します。
iPhone Simulatorではカメラが利用できないので実機にて実行してください。iPhone 実機に声をかけたり音声を鳴らしてみてください。音声が出力され、上側に Local のカメラ映像、下側に Remote の受信映像が描画されれば成功です。
次のステップ
今回は SFU サーバーを介して自分が Publish したメディアを Subscribe するというシンプルなものでした。
次は、他のクライアントと映像・音声・データをやりとりしてみましょう。他のクライアントと疎通できるサンプルアプリケーションを用意しています。
また、開発の前に開発ドキュメントもご一読ください。