リアルタイムメッセージング機能

概要

リアルタイムメッセージング機能は WebRTC の DataChannel を利用して、データの送受信を行う機能です。

詳細は Sora ドキュメント リアルタイムメッセージング機能 をご確認ください。 また、サンプルアプリケーション 内に DataChannel サンプルを用意していますのでこちらも参考にしてください。

メッセージを送受信する

Sora 接続時にリアルタイムメッセージング用 DataChannel を設定する

Sora の接続時に Configuration.dataChannels にリアルタイムメッセージング用 DataChannel を指定できます。 JSONSerialization で JSON に変換可能な形式で指定します。

// 接続の設定
var config = Configuration(url: soraURL,
                           channelId: soraChannelId,
                           role: role)

// DataChannel 経由のシグナリングを有効にする
config.dataChannelSignaling = true

// リアルタイムメッセージング機能に利用する DataChannel を指定する
config.dataChannels = [[
          "label": "#spam",
          "direction": "sendrecv",
      ], [
          "label": "#egg",
          "max_retransmits": 0,
          "ordered": false,
          "protocol": "abc",
          "compress": false,
          "direction": "recvonly",
          "header": [
              ["type": "sender_connection_id"]
          ],
]]

DataChannel の利用開始を確認する

onDataChannel にて、リアルタイムメッセージング用 DataChannel が利用可能になった通知を受け取ることができます。

onDataChannel はメッセージング用ラベル ( # 始まりのラベル) が、すべてクライアント側で OPEN になったタイミングで呼び出されます。

config.mediaChannelHandlers.onDataChannel = { mediaChannel in
  print("messaging data channels are available")
}

メッセージを送信する

MediaChannel.sendMessage を利用してメッセージの送信を行います。バイナリデータ (Data 型) の送信のみ可能です。文字列を送信したい場合はバイナリデータに変換してください。

MediaChannel.sendMessage は、対象ラベルの DataChannel が OPEN になった後に利用できます。たとえば #spam に送信したい場合は、 onDataChannelOpened#spamOPEN を確認してから MediaChannel.sendMessage を呼び出します。 メッセージング用ラベル (# 始まり) がすべて送受信可能になったタイミングをまとめて確認したい場合は onDataChannel を利用できます。

// メッセージを送信する
// 対象ラベルの onDataChannelOpened を受信した後から送信できる
let error = mediaChannel.sendMessage(label: "#spam", data: "egg".data(using: .utf8)!)
guard error == nil else {
    // エラー処理
    ...
    return
}

メッセージを受信する

Configuration.mediaChannelHandlers.onDataChannelMessage を利用してメッセージの受信を行います。バイナリデータ (Data 型) での受信になりますので、適宜必要な型に変換してください。

// メッセージ受信時の処理をハンドラーに定義する
config.mediaChannelHandlers.onDataChannelMessage = { (mediaChannel, label, data) in
  // label をチェックする
  guard label.starts(with: "#spam") else {
    return
  }
  ...
}

たとえば、 UTF-8 の文字列として受信したい場合は String(data:encoding:) を利用します。

config.mediaChannelHandlers.onDataChannelMessage = { (_, label, data) in
  guard label == "#spam" else {
    return
  }

  // UTF-8 文字列として受信する
  if let message = String(data: data, encoding: .utf8) {
    print("string message: \(message)")
    return
  }
}

ヘッダーとメッセージを分割する

リアルタイムメッセージングにヘッダーが追加されている場合、 onDataChannelMessage で受け取るデータにはヘッダーとメッセージが結合された状態で入っています。これを分離するには、 Configuration.mediaChannelHandlersonReceiveSignalingonDataChannelMessage を利用して以下のように処理します。

// dataChannels の label ごとにヘッダーの長さを保持するための変数
var headerLengths: [String: Int] = [:]

// offer で入ってくる data channel header の長さを取得する
config.mediaChannelHandlers.onReceiveSignaling = { [weak self] signaling in
    guard let self else {
        return
    }
    switch signaling {
    case let .offer(offer):
        guard let dataChannels = offer.dataChannels else {
            return
        }
        for dataChannel in dataChannels {
            // ラベルが "#" で始まる場合のみ処理する
            let label: String = dataChannel["label"] as! String
            guard label.starts(with: "#") else {
                continue
            }

            // dataChannel["header"] が nil ではない場合のみ後続処理を行う
            guard let headers = dataChannel["header"] as? [[String: Any]] else {
                continue
            }
            for header in headers {
                if header["type"] as! String == "sender_connection_id" {
                    let length = header["length"] as! Double
                    headerLengths[label] = Int(length)
                    print("kensaku: \(label) \(Int(length))")
                }
            }
        }
    default:
        break
    }
}

// メッセージ受信時の処理を定義する
config.mediaChannelHandlers.onDataChannelMessage = { [weak self] _, label, data in
    guard let self = self else {
        return
    }

    // "#" で始まるラベル以外は無視する
    guard label.starts(with: "#") else {
        return
    }

    // ヘッダーの長さを取得し、ヘッダーとメッセージを分離する
    let headerLength = self.headerLengths[label] ?? 0
    if headerLength == 0 {
        print(String(data: data, encoding: .utf8) ?? data.map { String($0) }.joined(separator: ", "))
        return
    }
    let header = data.prefix(headerLength)
    let message = data.suffix(from: headerLength)
    ...
}

リアルタイムメッセージングのみで接続する

音声と映像を送受信せずにメッセージのみの送受信を行うことができます。

詳細については Sora のドキュメント 音声と映像を送受信せずにメッセージのみで接続する をご確認ください。

// 接続の設定
var config = Configuration(url: soraURL,
                           channelId: soraChannelId,
                           role: .sendrecv)

// 音声と映像を送受信しない
config.audioEnabled = false
config.videoEnabled = false

// DataChannel 経由のシグナリングを有効にする
config.dataChannelSignaling = true

// リアルタイムメッセージング機能に利用する DataChannel を指定する
config.dataChannels = [[
          "label": "#spam",
          "direction": "sendrecv",
]]