iOSアプリでGoogle Nest CamにWebRTC接続

yutailang0119.hatenablog.com

の続編

WebRTC

webrtc.org

Web Real-Time Communicationの略で、動画や音声、データの相互通信する規格のことです。

It supports video, voice, and generic data to be sent between peers

WebRTC API - Web APIs | MDNが詳しく解説されています。

iOSでのWebRTCライブラリ

iOSアプリでGoogle Smart Device Management APIを使う (2025) - がんばってなんか書くでは

WebRTCのライブラリがないので、情報お待ちしております!

と終わったので、その後の調査から。

残念なことに、Googleからのモバイル向けの公式バイナリは配布停止されています。
PSA: WebRTC M80 Release Notes

WebRTC自体はオープンソースプロジェクトとして公開されているので、https://webrtc.googlesource.com/src を自分でコンパイルすれば使用可能です。
が、Swift Package Managerで手軽に管理したいので、今回は stasel/WebRTC を利用することにします。
Binary Targetとして、ビルド済みxcframeworkをリンクしてくれるので便利。

Smart Device Management APIを介したWebRTC接続

WebRTCの魅力の一つは相互に送受信できることですが、Nest Cam側の映像、音声を受信することに絞って考えます。
Nest Camにはスピーカーが付いているので、音声を送信することもできそうな気がする。
NSMicrophoneUsageDescriptionが必要になるはず。

手順

  1. RTCPeerConnectionを作成 w/RTCDataChannel
  2. SDP Offerを生成
  3. SDM APIにSDP Offerを送信
    • sdm.devices.commands.CameraLiveStream.GenerateWebRtcStream
  4. SDP Answerを受信
  5. RTCPeerConnectionにAnswer SDPを適用
  6. 受信開始

WebRTC接続確立手順
Online FlowChart & Diagrams Editor - Mermaid Live Editor

1. RTCPeerConnectionを作成 w/RTCDataChannel

Factoryが用意されているので、必要な設定を詰めてRTCPeerConnectionを作成。
RTCDataChannelも接続します。

import WebRTC

let factory = RTCPeerConnectionFactory()
let constraints = RTCMediaConstraints(
  mandatoryConstraints: [
    kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueTrue,
    kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueTrue,
  ],
  optionalConstraints: nil
)
guard let peerConnection = factory.peerConnection(
  with: RTCConfiguration(),
  constraints: constraints,
  delegate: nil
) else {
  return
}
let dataChannelConfiguration = RTCDataChannelConfiguration()
dataChannelConfiguration.isOrdered = true
_ = peerConnection.dataChannel(forLabel: "foobar", configuration: dataChannelConfiguration)

2. SDP Offerを生成

RTCPeerConnectionから、OfferのSDPを生成。
RTCPeerConnection にLocal descriptionとして、適用します。

do {
  let offer = try await peerConnection.offer(for: constraints)
  try await peerConnection.setLocalDescription(offer)
} catch { ... }

3. SDM APIにSDP Offerを送信

Smart Device Management APIのCommandを使って、WebRTCを生成します。

sdm.devices.commands.CameraLiveStream.GenerateWebRtcStream

POST /enterprises/project-id/devices/device-id:executeCommand
{
  "command" : "sdm.devices.commands.CameraLiveStream.GenerateWebRtcStream",
  "params" : {
    "offerSdp" : "\(offer.sdp)"
  }
}

4. SDP Answerを受信

APIレスポンスの answerSdp を使います。

{
  "results" : {
    "answerSdp" : "answerSdp",
    "expiresAt" : "2020-01-04T18:30:00.000Z",
    "mediaSessionId" : "Bshco5XLhPxzdJYfQn7Kwtp4yd..."
  }
}

5. RTCPeerConnectionにAnswer SDPを適用

RTCPeerConnection にRemote descriptionとして、適用します。

do {
  ...
  try await peerConnection.setRemoteDescription(
    RTCSessionDescription(type: .answer, sdp: response.results.answerSdp)
  )
} catch { ... }

6. 受信開始

RTCPeerConnectionからRTCVideoTrackを取り出し、

let videoTrack = peerConnection.transceivers
  .first(where: { $0.mediaType == .video })?
  .receiver.track as? RTCVideoTrack

RTCMTLVideoView に表示

import SwiftUI

struct VideoView: UIViewRepresentable {
  var videoTrack: RTCVideoTrack

  func makeUIView(context: Context) -> RTCMTLVideoView {
    let view = RTCMTLVideoView()
    view.videoContentMode = .scaleAspectFit
    return view
  }

  func updateUIView(_ uiView: RTCMTLVideoView, context: Context) {
    videoTrack.add(uiView)
  }
}

表示

youtu.be

Desing for iPadとして、macOSやvisionOSでも実行可能です。

おわり

宣伝

さらにこの先の拡張について、iOSDC Japan 2025にプロポーザルを応募しています。
お気に入り、応援よろしくお願いします!!!