MediaChannel

public final class MediaChannel
extension MediaChannel: CustomStringConvertible

一度接続を行ったメディアチャネルは再利用できません。 同じ設定で接続を行いたい場合は、新しい接続を行う必要があります。

接続が解除されるタイミング

メディアチャネルの接続が解除される条件を以下に示します。 いずれかの条件が 1 つでも成立すると、メディアチャネルを含めたすべてのチャネル (シグナリングチャネル、ピアチャネル、 WebSocket チャネル) の接続が解除されます。

  • シグナリングチャネル (SignalingChannel) の接続が解除される。
  • WebSocket チャネル (WebSocketChannel) の接続が解除される。
  • ピアチャネル (PeerChannel) の接続が解除される。
  • サーバーから受信したシグナリング ping に対して pong を返さない。 これはピアチャネルの役目です。

イベントハンドラ

  • イベントハンドラ

    get / set は lock 付き storage で排他します。配送は毎回この storage から読むため、接続を 開始した後に代入しても次の配送から反映されます。配送側は lock を解放してから取得済みの closure を呼びます。この storage が排他するのは参照の差し替えだけで、各イベントハンドラの プロパティは MediaChannelHandlers の HandlerStorage が排他します。

    Declaration

    Swift

    public var handlers: MediaChannelHandlers { get set }

イベントの購読

  • 接続イベントを購読します。

    購読者ごとに独立した AsyncStream を返します。同じ MediaChannel に対して複数回呼ぶと、 それぞれが独立した buffer / drop 方針 / 終端を持ちます (1 つの AsyncStream を複数の Iterator で消費するとイベントが購読者間で分かれるため、購読者ごとに stream を作ります)。

    • イベントは接続単位の順序付きの列です。同じ接続のイベントは配送順に届きます。通し番号の 順序と一致しない場合はあります (同時に配送されたイベントのみ)。
    • buffer は既定で SoraEvent.defaultBufferSize 件です。あふれた場合は最も古いイベントが 破棄されます。sequence で欠落を検出できます。
    • 購読を開始する前に配送されたイベントは届きません (buffer は購読ごとに作られます)。 接続の完了前に開始するには、SoraHandlers.onAddMediaChannel または Sora.mediaChannels で 参照を取得してから購読するか、Sora.subscribeEvents(bufferingPolicy:) を使います。
    • connected と connectFailed は接続の完了と同じ配送点で発行されます。接続の完了を 待ってから購読すると取り逃すため、接続の結果を購読する場合は Sora の購読を使ってください。
    • payload はすべて値として確定しており、配送後も購読者が保持できます。mutable な MediaChannel / MediaStream / raw WebRTC object は含まれません。SoraEvent は Sendable のため、actor / Task 境界へそのまま渡せます。
    • 購読の解除は、購読している Task の cancel、購読に使った AsyncStream への参照の解放、 または接続の終了です。購読を解除しても他の購読者の buffer と終端は影響を受けません。
    • 接続が終了すると .disconnected を配送した後に stream が終端します。終端後の購読には 終端済みの stream を返します。.disconnected を配送せずに終端するのは、明示切断を経由せず MediaChannel が解放された場合だけです。
    • 購読している Task の loop の中から同期 API (connectionId / state などの getter、 sendMessage、disconnect) を呼べます。配送は排他区間の外で行うため deadlock しません。 disconnect を呼ぶと .disconnected の配送後に購読が終端し、以降のイベントは届きません。
    • 配送 executor はイベントの発生元によって異なります (libwebrtc の callback スレッド、 signaling の受信スレッド、呼び出し元の executor など)。AsyncStream の再開後に実行される 購読者のコードは、購読している Task の executor 上で動きます。UI 更新は main actor / main queue へ束ねてください。
    • videoEnabledChanged / audioEnabledChanged は、並行する変更の確定順と配送順が一致しない ことがあります (MediaStreamHandlers.onSwitchVideo と同じ契約です)。
    • この API の追加によって、既存の handlers の callback 型・配送 executor・配送順序・ 発火回数は変わりません。

    Declaration

    Swift

    public func subscribeEvents(
      bufferingPolicy: AsyncStream<SoraEvent>.Continuation.BufferingPolicy = .bufferingNewest(
        SoraEvent.defaultBufferSize)
    ) -> AsyncStream<SoraEvent>

    Parameters

    bufferingPolicy

    購読者ごとの buffer と drop 方針

    Return Value

    この接続のイベントを配送する stream

接続情報

  • クライアントの設定

    公開互換のために利用者が渡した値を返し続けます。接続開始後の非同期区間 (非同期 hop の後、WebRTC callback、ConnectionTimer) はこの値の参照型フィールド (metadata / notify metadata / codec 別 params / dataChannels / forwardingFilter / forwardingFilters / webRTCConfiguration、および snapshot に含めないハンドラクラスと audioDevice) を読みません。値型フィールドは接続開始時の値のままなので、公開 getter、 description、公開 mute API、senderStream / receiverStreams はこの値を読みます。

    Declaration

    Swift

    public let configuration: Configuration
  • 最初に type: connect メッセージを送信した URL (デバッグ用)

    Sora から type: redirect メッセージを受信した場合、 contactUrl と connectedUrl には異なる値がセットされます type: redirect メッセージを受信しなかった場合、 contactUrl と connectedUrl には同じ値がセットされます

    Declaration

    Swift

    public var contactUrl: URL? { get }
  • 接続中の URL

    Declaration

    Swift

    public var connectedUrl: URL? { get }
  • メディアチャンネルの内部で利用している RTCPeerConnection

    Declaration

    Swift

    public var native: RTCPeerConnection? { get }
  • クライアント ID 。接続後にセットされます。

    Declaration

    Swift

    public var clientId: String? { get }
  • バンドル ID 。接続後にセットされます。

    Declaration

    Swift

    public var bundleId: String? { get }
  • 接続 ID 。接続後にセットされます。

    Declaration

    Swift

    public var connectionId: String? { get }
  • 接続状態

    接続状態の正本は stateStorage だけであり、この property はそこを読む computed property です。 getter だけの宣言には private(set) を付けられないため get / set を明示し、set は setState(_:) を呼びます (storage を直接書きません)。読み書きの排他は storage の NSLock に 揃います。

    遷移ログは排他区間の外で出す。didSet では lock を保持したまま Logger を呼び得るため、 connectionLifecycleLock を保持して遷移させる箇所では、遷移の直後 (lock の解放後) に 呼び出し元が logStateChange(from:) を呼ぶ (この property は computed property のため didSet を持たない)。

    接続状態の遷移は setState(_:) を呼ぶこと。private(set) の setter は connectionLifecycleLock を取らずに setState(_:) へ入るため、SDK 内部から state へ 代入してよいのは connectionLifecycleLock を保持した区間だけです。

    Declaration

    Swift

    public private(set) var state: ConnectionState { get set }
  • 接続中 (state == .connected) であれば true

    Declaration

    Swift

    public var isAvailable: Bool { get }
  • 接続開始時刻。 接続中にのみ取得可能です。

    Declaration

    Swift

    public private(set) var connectionStartTime: Date? { get }
  • 接続時間 (秒) 。 接続中にのみ取得可能です。

    Declaration

    Swift

    public var connectionTime: Int? { get }

接続中のチャネルの情報

  • 同チャネルに接続中のクライアントの数。 サーバーから通知を受信可能であり、かつ接続中にのみ取得可能です。

    Declaration

    Swift

    public private(set) var connectionCount: Int? { get }
  • 同チャネルに接続中のクライアントのうち、パブリッシャーの数。 サーバーから通知を受信可能であり、接続中にのみ取得可能です。

    Declaration

    Swift

    public private(set) var publisherCount: Int? { get }
  • 同チャネルに接続中のクライアントの数のうち、サブスクライバーの数。 サーバーから通知を受信可能であり、接続中にのみ取得可能です。

    Declaration

    Swift

    public private(set) var subscriberCount: Int? { get }

DataChannel の OPEN 追跡

  • ストリームのリスト

    Declaration

    Swift

    public var streams: [MediaStream] { get }
  • 最初のストリーム。 マルチストリームでは、必ずしも最初のストリームが 送信ストリームとは限りません。 送信ストリームが必要であれば senderStream を使用してください。

    Declaration

    Swift

    public var mainStream: MediaStream? { get }
  • 送信に使われるストリーム。 ストリーム ID が configuration.publisherStreamId に等しいストリームを返します。

    Declaration

    Swift

    public var senderStream: MediaStream? { get }
  • 受信ストリームのリスト。 ストリーム ID が configuration.publisherStreamId と異なるストリームを返します。

    Declaration

    Swift

    public var receiverStreams: [MediaStream] { get }

RPC

  • RPC メソッドを型安全に呼び出します

    このメソッドを使用して、Sora サーバーで定義された RPC メソッドを非同期で実行できます。

    actor 境界や Task の @Sendable closure へ結果を渡す場合は、 sendableRPC(method:params:isNotificationRequest:timeout:) を使用してください。

    Throws

    以下のエラーが発生することがあります
    • SoraError.rpcUnavailable: RPC チャネルが利用不可
    • SoraError.rpcEncodingError: パラメータのエンコーディングに失敗した
    • SoraError.rpcDecodingError: レスポンスのデコーディングに失敗した
    • SoraError.rpcDataChannelClosed: RPC の送受信に利用する DataChannel が切断された
    • SoraError.rpcTimeout: レスポンスがタイムアウト時間内に返されなかった
    • SoraError.rpcServerError: Sora からエラーレスポンスがあった (詳細は RPCErrorDetail、追加情報は JSONValue? の data)
    • CancellationError: タスクがキャンセルされた

    使用例

    do {
      let response = try await mediaChannel.rpc(
        method: RequestSimulcastRid.self,
        params: RequestSimulcastRidParams(rid: "r0")
      )
    
      if let result = response?.result {
        print("Channel ID: \(result.channelId)")
      }
    } catch {
      print("RPC call failed: \(error)")
    }
    

    Declaration

    Swift

    public func rpc<M: RPCMethodProtocol>(
      method: M.Type,
      params: M.Params,
      isNotificationRequest: Bool = false,
      timeout: TimeInterval = 5.0
    ) async throws -> RPCResponse<M.Result>?

    Parameters

    method

    呼び出す RPC メソッドの型 (例: RequestSimulcastRid.self)

    params

    メソッドに渡すパラメータ。型安全に検証されます

    isNotificationRequest

    true の場合、送信後に Sora からのレスポンスを待ちません。デフォルトは false

    timeout

    レスポンスを待つ最大時間(秒)。デフォルトは 5.0 秒

    Return Value

    メソッドの実行結果。isNotificationRequest が true の場合は nil を返します

  • rpc(method:params:isNotificationRequest:timeout:) と同じ挙動で、Swift 6 言語モードの検査に対応した RPC メソッドを型安全に呼び出します

    引数の意味、戻り値の意味 (notification では nil が返ること)、返るエラー、タスクキャンセルと response / timeout / DataChannel 切断が競合した場合の pending の終端は rpc と共通です (引数とエラーの詳細は rpc(method:params:isNotificationRequest:timeout:) を参照してください)。 違うのは、SendableRPCMethodProtocol に準拠したメソッドだけを呼べる点と、戻り値が SendableRPCResponse<M.Result>? になる点です。params と result が Sendable であるため、 戻り値は actor 境界や Task の @Sendable closure を越えて受け渡せます。

    新旧両方の protocol へ準拠した型でも、rpc の戻り値は RPCResponse<M.Result>? のままです (別名の API のため overload の解決先が変わりません)。

    使用例

    do {
      let response = try await mediaChannel.sendableRPC(
        method: RequestSimulcastRid.self,
        params: RequestSimulcastRidParams(rid: "r0")
      )
    
      if let result = response?.result {
        print("Channel ID: \(result.channelId)")
      }
    } catch {
      print("RPC call failed: \(error)")
    }
    

    Declaration

    Swift

    public func sendableRPC<M: SendableRPCMethodProtocol>(
      method: M.Type,
      params: M.Params,
      isNotificationRequest: Bool = false,
      timeout: TimeInterval = 5.0
    ) async throws -> SendableRPCResponse<M.Result>?

接続

  • 接続を解除します。

    Declaration

    Swift

    public func disconnect(error: Error?)

    Parameters

    error

    接続解除の原因となったエラー

  • libwebrtc の統計情報を取得します。

    非同期取得中に切断された場合でも安全になるよう、コールバック内で チャンネルの解放が始まっていないこと (MediaChannelStateStorage の終端フラグ)、 チャンネルの生存確認、state == .connected の再確認、peerChannel.nativeChannel が 同一インスタンスかどうか、をチェックしています。

    handler の実行スレッドは経路で異なります。RTCPeerConnection.statistics の完了 block から 呼ばれる経路 (成功と、終端フラグ / state / nativeChannel の同一性判定で失敗する場合) の handler は libwebrtc 側のスレッドから呼ばれ、入口の前段判定 (未接続 / nativeChannel が nil) で失敗する経路の handler は呼び出し元のスレッドから同期的に呼ばれます。どちらの経路も 呼び出し元スレッドは保証されません。UI 更新や共有状態の変更は main queue / main actor へ 束ねてください。

    Swift 6 言語モードでは、handler の中で first(where:) などの closure を呼ばず、 handler の中の closure に @Sendable を付けて Sendable な値へ詰め替えてから main actor / main queue へ渡すか、handler の先頭で main に束ねます。handler の中で closure を呼ぶと、 MainActor 隔離を継承した closure が WebRTC スレッドで実行時隔離チェックに掛かります。 handler 引数の型 (@escaping (Result<Statistics, Error>) -> Void) は変更しません。

    actor / Task 境界へ統計情報を渡す場合は、getStatsSnapshot(handler:) が handler へ渡す値と getStatsSnapshot() が返す値が immutable で deep Sendable なため、こちらを使用してください。 Statistics は mutable class のため、そのまま境界を越えて渡せません。

    Declaration

    Swift

    public func getStats(handler: @escaping (Result<Statistics, Error>) -> Void)

    Parameters

    handler

    統計情報取得後に呼ばれるクロージャー

Statistics snapshot

  • libwebrtc の統計情報を、actor / Task 境界へ渡せる snapshot として取得します。

    getStats(handler:) と同じ RTCPeerConnection.statistics を入力源にし、戻り値だけを immutable で deep Sendable な StatisticsSnapshot に変えます。既存の getStats(handler:) の シグネチャと挙動、Statistics / StatisticsEntry は変更しません。

    handler の実行スレッドは getStats(handler:) と同じです。RTCPeerConnection.statistics の 完了 block から呼ばれる経路 (成功と、終端フラグ / state / nativeChannel の同一性判定 / 統計値の変換で失敗する場合) の handler は libwebrtc 側のスレッドから呼ばれ、入口の前段判定 (未接続 / nativeChannel が nil) で失敗する経路の handler は呼び出し元のスレッドから 同期的に呼ばれます。どちらの経路も呼び出し元スレッドは保証されません。

    Swift 6 言語モードでの扱い方も getStats(handler:) と同じです (handler の中で closure を 呼ばず、handler の中の closure に @Sendable を付けて Sendable な値へ詰め替えてから main actor / main queue へ渡すか、handler の先頭で main に束ねます)。

    失敗時の SoraError.peerChannelError の reason は、入口の前段判定で "MediaChannel is not connected (state: …)" と "RTCPeerConnection is unavailable (nativeChannel: nil)"、 完了 block の判定で "MediaChannel is unavailable" と "MediaChannel is not connected (state: …)" と "RTCPeerConnection is unavailable (nativeChannel changed)" になります。統計値を JSONValue へ 変換できない場合は SoraError.mediaChannelError になります。

    Declaration

    Swift

    public func getStatsSnapshot(handler: @escaping (Result<StatisticsSnapshot, Error>) -> Void)

    Parameters

    handler

    統計情報取得後に呼ばれるクロージャー

  • getStatsSnapshot() Asynchronous

    libwebrtc の統計情報を、actor / Task 境界へ渡せる snapshot として取得します。

    getStatsSnapshot(handler:) の async 版です。タスクのキャンセルは CancellationError で 終端します。libwebrtc には統計取得をキャンセルする API が無いため、キャンセル後も RTCPeerConnection.statistics は開始して完了 block まで走りますが、終端済みの結果は 返しません (キャンセルが要求の開始より先でも、開始の有無で終端の挙動を分けません)。

    このメソッドは呼び出し中 MediaChannel を保持します。そのため deinit による切断と 後始末は、完了 block が届くかタスクがキャンセルされるまで遅延します。完了 block が届かない 場合に await を終わらせる手段はタスクキャンセルだけです (既存 rpc のような timeout は 持ちません)。また、呼び出し中は MediaChannel が生存するため、完了 block の終端フラグ (MediaChannelStateStorage.isTerminated) が真になることはなく、解放開始による失敗は getStatsSnapshot(handler:) だけが返します。

    キャンセルは async 版だけを対象とし、キャンセル・切断・状態遷移が競合しても終端は 1 回です。 キャンセルと結果の到着が競合した場合は、先に終端した方を採用します (キャンセルが先なら CancellationError、結果が先なら snapshot を返します)。入口の前段判定 (未接続 / nativeChannel が nil) は getStatsSnapshot(handler:) と共通で、キャンセル済みのタスクでは CancellationError が優先されます。

    Throws

    SoraError.peerChannelError (未接続 / nativeChannel が nil / 接続状態の遷移後 / nativeChannel の差し替え後。reason は getStatsSnapshot(handler:) と同じ)、 SoraError.mediaChannelError (統計値を JSONValue へ変換できない)、 CancellationError (タスクがキャンセルされた)

    Declaration

    Swift

    public func getStatsSnapshot() async throws -> StatisticsSnapshot

    Return Value

    統計情報の snapshot

Statistics 共通の判定

  • DataChannel を利用してメッセージを送信します

    Declaration

    Swift

    public func sendMessage(label: String, data: Data) -> Error?
  • MediaChannel の接続中にマイクをハードミュート有効化/無効化します

    Declaration

    Swift

    public func setAudioHardMute(_ mute: Bool) -> Error?

    Parameters

    mute

    true で有効化、false で無効化

    Return Value

    成功した場合は nil、失敗した場合は SoraError.mediaChannelError を返します

  • MediaChannel の接続中にマイクをソフトミュート有効化 / 無効化します

    この呼び出し 1 回を 1 operation とし、MediaStream.audioEnabled への直接代入とは operation の世代 (operation の開始時に取得した順) で調停されます。後から開始した operation の 値が最新になり、より大きい世代が先に確定している場合、この呼び出しの書き込みは破棄されます (呼び出しは成功を返します)。

    Declaration

    Swift

    public func setAudioSoftMute(_ mute: Bool) -> Error?

    Parameters

    mute

    true で有効化、false で無効化

    Return Value

    成功した場合は nil、失敗した場合は SoraError.mediaChannelError を返します

  • MediaChannel の接続中に映像をソフトミュート有効化 / 無効化します 黒塗りフレームが送信される状態になります

    この呼び出し 1 回を 1 operation とし、setVideoHardMute と MediaStream.videoEnabled への 直接代入とは operation の世代 (operation の開始時に取得した順) で調停されます。後から開始した operation の値が最新になり、より大きい世代が先に確定している場合、この呼び出しの書き込みは 破棄されます (呼び出しは成功を返します)。

    Declaration

    Swift

    public func setVideoSoftMute(_ mute: Bool) -> Error?

    Parameters

    mute

    true で有効化、false で無効化

    Return Value

    成功した場合は nil、失敗した場合は SoraError.mediaChannelError を返します

  • setVideoHardMute(_:) Asynchronous

    MediaChannel の接続中に映像をハードミュート有効化 / 無効化します

    端末カメラ利用が有効になっている必要があります 外部入力や別キャプチャ経路には対応していません

    内部で Actor により、操作を排他実行します。 同時に呼び出された場合は Actor 側で SoraError.mediaChannelError がスローされます

    映像ハードミュートは、黒塗りフレーム状態で停止させるためローカルトラックの停止を含みます 事前に映像ソフトミュートを利用していた場合は状態が上書きされます ハードミュート解除時に直前のソフトミュートの状態を復元するようなことはしません

    ハードミュート有効化に失敗した場合は、VideoHardMuteActor の直列化区間へ入った時点の senderStream.videoEnabled を復元します。ただし操作が取り消された場合は復元せず、 黒塗り (ソフトミュート) のまま終了します。 切断の開始と同時に失敗した場合は復元されることがあります。 ただしこの基準値は呼び出し直前の値と厳密に一致する保証はありません。

    この呼び出し 1 回を 1 operation とし、videoEnabled の設定・復元・ハードミュート解除後の 有効化はすべてこの呼び出しが公開 API の入口で取得した世代で確定します。setVideoSoftMute や MediaStream.videoEnabled への直接代入など、後から開始した operation が先に値を確定している 場合は、この操作の書き込み (解除後の有効化を含む) が破棄されます (この操作の失敗時の復元も 破棄されます)。 操作の実行中や設定前の取消により拒否された場合は videoEnabled を変更しません。

    この操作の書き込みが破棄された場合でも、ハードミュートのカメラ停止・再開は実行されます。 その場合 videoEnabled の確定値と実カメラ状態が食い違うことがあり、その場合は復旧に setVideoHardMute(false) が必要です (setVideoSoftMute(false) では復旧しません)。

    senderStream.videoEnabled の確定処理は値が変化したときだけ利用者 handler と VideoRenderer を呼びます (BasicMediaStream の経路は setter ではなくこの確定処理を直接呼びます)。 この書き込みが後続の operation により破棄されない場合に限り、呼び出し前が有効なときは成功時に onSwitchVideo(false) が 1 回、復元する失敗時に onSwitchVideo(false) と onSwitchVideo(true) が この順に 1 回ずつ発火します。 有効化の経路ではこれらの handler は VideoHardMuteActor の executor で発火します。 これに対し VideoRenderer.onSwitch(video:) の配送 executor は main queue であり、 handler と renderer の相対順序は保証されません。

    Throws

    エラー時は SoraError.cameraError または SoraError.mediaChannelError がスローされます

    Declaration

    Swift

    public func setVideoHardMute(_ mute: Bool) async throws

    Parameters

    mute

    true で有効化、false で無効化

  • MediaChannel の接続中に ReplayKit を利用して画面キャプチャおよび映像配信を開始します

    送信フレームレートは ScreenCaptureSettings.targetFPS で制御できます。

    ScreenCaptureSettings.videoSampleBufferTransformer は SDK 内部の送信キュー上で呼ばれます。 targetFPS による間引きで破棄されるフレームと、送信処理中のために破棄されるフレーム、 キャプチャ停止中と切断中のフレームでは呼ばれません。引数と戻り値の CMSampleBuffer の 所有権は SDK に委ねられ、戻り値の pixel buffer は送信のために SDK が retain します。 戻り値を返した後にその buffer を書き換えないでください。

    同一 senderStream に対してカメラキャプチャが動作中の場合は開始できません。 接続前に Configuration.initialCameraEnabled = false を設定してください。 接続後にカメラを停止する場合は setVideoHardMute(true) を先に呼んでください。

    Throws

    エラー時は SoraError.mediaChannelError または ReplayKit 起因のエラーがスローされます

    Declaration

    Swift

    public func startScreenCapture(settings: ScreenCaptureSettings = .init()) async throws

    Parameters

    settings

    画面キャプチャ設定

  • stopScreenCapture() Asynchronous

    ReplayKit を利用した画面キャプチャを停止します

    Declaration

    Swift

    public func stopScreenCapture() async
  • 画面キャプチャが動作中かを取得します

    Declaration

    Swift

    public func isScreenCaptureActive() -> Bool