リアルタイムメッセージング機能¶
概要¶
リアルタイムメッセージング機能は WebRTC の DataChannel を利用して、データの送受信を行う機能です。 詳細は Sora のドキュメント を参照してください。 また、サンプルアプリケーションに リアルタイムメッセージング を用意していますのでこちらも参考にしてください。
Sora Android SDK では SoraMediaChannel の初期化時に dataChannels に compress = true を指定した場合、メッセージの圧縮と解凍は SDK 内部で自動的に行われます。アプリケーション側で意識する必要はありません。
メッセージを送受信する¶
Sora 接続時にリアルタイムメッセージング用 DataChannel を設定する¶
Sora の 接続時に SoraMediaChannel にリアルタイムメッセージング用の DataChannel を指定できます。 List<Map<String, Any>>
の形式で指定します。
// リアルタイムメッセージング機能に利用する DataChannel を指定する
val dataChannels = listOf(
mapOf(
"label" to "#spam",
"direction" to "sendrecv"
),
mapOf(
"label" to "#egg",
"max_retransmits" to 0,
"ordered" to false,
"protocol" to "abc",
"compress" to false,
"direction" to "recvonly",
"header" to listOf(
mapOf(
"type" to "sender_connection_id"
)
)
),
)
// オプションを設定する。通常の接続と設定内容は変わりません。
val mediaOption = SoraMediaOption()
mediaOption.enableMultistream()
mediaOption.enableVideoDownstream(null)
// Sora に接続する。SoraMediaChannel 生成時に dataChannels を設定する。
val mediaChannel = SoraMediaChannel(
context = this,
signalingEndpointCandidates = [
"wss://sora.example.com/signaling",
],
channelId = "sora",
mediaOption = option,
listener = channelListener,
// DataChannel シグナリングを有効化する
dataChannelSignaling = true,
dataChannels = dataChannels)
mediaChannel.connect()
メッセージを受信する¶
SoraMediaChannel.Listener.onDataChannel にて DataChannel の開始通知を受け取ることができます。また、利用可能な DataChannel 情報の参照が可能です。
SoraMediaChannel.Listener.onDataChannelMessage にてメッセージの受信を行います。
val channelListener = object : SoraMediaChannel.Listener {
// DataChannel が利用可能になったタイミングで実行されるコールバック
override fun onDataChannel(mediaChannel: SoraMediaChannel, dataChannels: List<Map<String, Any>>?) {
super.onDataChannel(mediaChannel, dataChannels)
// リアルタイムメッセージングで利用できる DataChannel 一覧を出力する
Log.d("onDataChannel", "$dataChannels")
}
}
// DataChannel メッセージ受信時のコールバック
override fun onDataChannelMessage(mediaChannel: SoraMediaChannel, label: String, data: ByteBuffer) {
// ByteBuffer を String に変換して出力する
val message = mediaChannel.dataToString(data)
Log.d("onDataChannelMessage", message)
}
}
メッセージを送信する¶
SoraMediaChannel.sendDataChannelMessage を利用してメッセージの送信を行います。String 型、ByteBuffer 型 に対応しています。
// メッセージ送信する
mediaChannel.sendDataChannelMessage("#spam", "Hello World")
DataChannel を利用したリアルタイムメッセージングのみで接続する¶
音声と映像を送受信せずにメッセージのみの送受信を行う方法の詳細については、 Sora のドキュメント 音声と映像を送受信せずにメッセージのみで接続する をご確認ください。 Sora Android SDK では、
SoraMediaOption.role
にSoraChannelRole.SENDRECV
を指定して接続を行います。
// オプションを設定する
val mediaOption = SoraMediaOption()
mediaOption.role = SoraChannelRole.SENDRECV
mediaOption.enableMultistream()
// Sora に接続する
val mediaChannel =
SoraMediaChannel(
context = this,
signalingEndpointCandidates =
[
"wss://sora.example.com/signaling",
],
channelId = "sora",
mediaOption = option,
listener = channelListener,
// DataChannel シグナリングを有効化する
dataChannelSignaling = true,
dataChannels = dataChannels
)
mediaChannel.connect()
ヘッダーとメッセージを分割する¶
リアルタイムメッセージングにヘッダーが追加されている場合、onDataChannelMessaging
で受け取るデータにはヘッダーとメッセージが結合された状態で入っています。
これを分離するには SoraMediaChannel.Listener の onOfferMessage
と onDataChannelMessage
を利用して以下のように処理します。
SoraMediaChannel.Listener.onOfferMessage でヘッダーの長さ(バイト数)を取得する
SoraMediaChannel.Listener.onDataChannelMessage で受けとったメッセージを先頭からヘッダーの長さ分とそれ以降に分割する
以下はヘッダーに sender_connection_id が指定されている場合の例です
// label ごとにヘッダーの長さを保持するための変数
private var headerLengthMap: MutableMap<String, Int> = mutableMapOf()
private val channelListener = object : SoraMediaChannel.Listener {
override fun onOfferMessage(mediaChannel: SoraMediaChannel, offer: OfferMessage) {
val dataChannels = offer.dataChannels
// dataChannels の null チェック
if (dataChannels != null) {
for (dataChannel in dataChannels) {
// label の 先頭に # がついているものがリアルタイムメッセージング用
// # がついていないものは処理しない
val label = dataChannel["label"] as String
if (!label.startsWith("#")) {
continue
}
// dataChannel に header (List<Map<String, Any>>) がある場合は
// header の中から type: sender_connection_id の Map を取得し
// length の値を取得する
val header = dataChannel["header"] as? List<*>?
if (header != null) {
for (headerMap in header) {
if (headerMap is Map<*, *>) {
if (headerMap["type"] == "sender_connection_id") {
// Double で取得されるため Int に変換する
val length = headerMap["length"] as Double
headerLengthMap[label] = length.toInt()
}
}
}
}
}
}
}
override fun onDataChannelMessage(
mediaChannel: SoraMediaChannel,
label: String,
data: ByteBuffer
) {
// headerLengthMap に label が存在するか確認する
// 存在する場合はその length を取得し、以下のように分割処理する
// 1. 先頭から length バイトが sender_connection_id
// 2. 残りがメッセージ本体
if (headerLengthMap.containsKey(label)) {
val length = headerLengthMap[label]!!
val senderConnectionId = ByteArray(length)
data.get(senderConnectionId)
val message = ByteArray(data.remaining())
data.get(message)
// String() で utf-8 文字列に変換する
Log.d(
"tag",
"received data: label=$label," +
"sender_connection_id=${String(senderConnectionId)}," +
"message=${String(message)}"
)
} else {
Log.d("tag", "received data: label=$label, message=${mediaChannel.dataToString(data)}")
}
}
}