diff --git a/CHANGELOG.md b/CHANGELOG.md
index 580e915d3cdbf56ae3e1051e6386f17e55f93520..280f099aa11e683b963fe4074217f8263a08b583 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- Event content types for VOIP.
+
### Changed
- `AccessTokenAuthenticationFunction` now allows servers to set `soft_logout`.
diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/ClientEvent.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/ClientEvent.kt
index 8ca29a7432ab6103bbce85ecbb56162540151bfa..ad51c5383d4254a789dbb87c1ece89e162f446bb 100644
--- a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/ClientEvent.kt
+++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/ClientEvent.kt
@@ -7,11 +7,16 @@ import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
/**
- * @see matrix spec
+ * A client event with a specific type given by the generic parameter C.
+ *
+ * @see Matrix events
*/
sealed interface ClientEvent : Event {
/**
- * @see matrix spec
+ * Matrix room event. Either a message event or a state event.
+ *
+ * @see Types of matrix room events
+ * @see Matrix room event format
*/
sealed interface RoomEvent : ClientEvent {
val id: EventId
@@ -20,6 +25,12 @@ sealed interface ClientEvent : Event {
val originTimestamp: Long
val unsigned: UnsignedRoomEventData?
+ /**
+ * Matrix message event
+ *
+ * @see Types of matrix room events
+ * @see Matrix room event format
+ */
@Serializable
data class MessageEvent(
@SerialName("content") override val content: C,
@@ -31,7 +42,10 @@ sealed interface ClientEvent : Event {
) : RoomEvent
/**
- * @see matrix spec
+ * Matrix state event
+ *
+ * @see Types of matrix room events
+ * @see Matrix room event format
*/
@Serializable
data class StateEvent(
diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/EventContent.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/EventContent.kt
index 29d4fb1a47ae823c6d3c5b995d54de04885194bb..69234568bacc22331917fa4d11e79d110001b6ba 100644
--- a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/EventContent.kt
+++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/EventContent.kt
@@ -7,6 +7,11 @@ import net.folivo.trixnity.core.model.events.m.RelatesTo
sealed interface EventContent
+/**
+ * Content of a matrix room event
+ *
+ * @see Types of matrix room events
+ */
sealed interface RoomEventContent : EventContent {
/**
* @see matrix spec
@@ -14,11 +19,17 @@ sealed interface RoomEventContent : EventContent {
val externalUrl: String?
}
+/**
+ * Content of a matrix message event
+ */
interface MessageEventContent : RoomEventContent {
val relatesTo: RelatesTo?
val mentions: Mentions?
}
+/**
+ * Content of a matrix state event
+ */
interface StateEventContent : RoomEventContent
interface ToDeviceEventContent : EventContent
diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/call/CallEventContent.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/call/CallEventContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..67c789cbaec9faac38a72972a9dcf556412187a3
--- /dev/null
+++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/call/CallEventContent.kt
@@ -0,0 +1,266 @@
+package net.folivo.trixnity.core.model.events.m.call
+
+import kotlinx.serialization.EncodeDefault
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.json.JsonDecoder
+import kotlinx.serialization.json.jsonPrimitive
+import net.folivo.trixnity.core.model.events.MessageEventContent
+import net.folivo.trixnity.core.model.events.m.Mentions
+import net.folivo.trixnity.core.model.events.m.RelatesTo
+
+/**
+ * Matrix call event content
+ *
+ * @see matrix spec
+ */
+sealed interface CallEventContent : MessageEventContent {
+ val callId: String
+ val version: String
+ val partyId: String?
+
+ /**
+ * Matrix call invite content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class Invite(
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String,
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String? = null,
+ @SerialName("invitee") val invitee: String? = null,
+ @SerialName("lifetime") val lifetime: Long,
+ @SerialName("offer") val offer: Offer,
+
+ // Added in v1.10:
+ @SerialName("sdp_stream_metadata") val sdpStreamMetadata: Map?,
+ ) : CallEventContent {
+ override val externalUrl: String? = null
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+
+ @Serializable
+ enum class OfferType {
+ @SerialName("offer") OFFER,
+ }
+
+ @Serializable
+ data class Offer(
+ @SerialName("sdp") val sdp: String,
+ @SerialName("type") val type: OfferType,
+ )
+ }
+
+ /**
+ * Matrix call candidates content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class Candidates(
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String,
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String? = null,
+ @SerialName("candidates") val candidates: List,
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+
+ @Serializable
+ data class Candidate(
+ @SerialName("candidate") val candidate: String,
+ @SerialName("sdpMLineIndex") val sdpMLineIndex: Long? = null,
+ @SerialName("sdpMid") val sdpMid: String? = null,
+ )
+ }
+
+ /**
+ * Matrix call answer content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class Answer(
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String,
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String? = null,
+ @SerialName("answer") val answer: Answer,
+
+ // Added in v1.10:
+
+ @SerialName("sdp_stream_metadata") val sdpStreamMetadata: Map? = null,
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+
+ @Serializable
+ enum class AnswerType {
+ @SerialName("answer") ANSWER,
+ }
+
+ @Serializable
+ data class Answer(
+ @SerialName("sdp") val sdp: String,
+ @SerialName("type") val type: AnswerType,
+ )
+ }
+
+ /**
+ * Matrix call hangup content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class Hangup(
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String,
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String? = null,
+ @SerialName("reason") val reason: Reason? = null,
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+
+ @Serializable
+ enum class Reason {
+ @SerialName("ice_failed") ICE_FAILED,
+ @SerialName("invite_timeout") INVITE_TIMEOUT,
+ // Added in v1.7:
+ @SerialName("ice_timeout") ICE_TIMEOUT,
+ @SerialName("user_hangup") USER_HANGUP,
+ @SerialName("user_media_failed") USER_MEDIA_FAILED,
+ @SerialName("user_busy") USER_BUSY,
+ @SerialName("unknown_error") UNKNOWN_ERROR,
+ }
+ }
+
+ // Added in v1.7:
+
+ /**
+ * Matrix call negotiate content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class Negotiate(
+ @EncodeDefault
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String = "1",
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String,
+ @SerialName("description") val description: Description,
+ @SerialName("lifetime") val lifetime: Long,
+
+ // Added in v1.10:
+
+ @SerialName("sdp_stream_metadata") val sdpStreamMetadata: Map? = null
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+
+ @Serializable
+ enum class DescriptionType {
+ @SerialName("offer") OFFER,
+ @SerialName("answer") ANSWER
+ }
+
+ @Serializable
+ data class Description(
+ @SerialName("sdp") val sdp: String,
+ @SerialName("type") val type: DescriptionType,
+ )
+ }
+
+ /**
+ * Matrix call reject content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class Reject(
+ @EncodeDefault
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String = "1",
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String,
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+ }
+
+ /**
+ * Matrix call select answer content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class SelectAnswer(
+ @EncodeDefault
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String = "1",
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String,
+ @SerialName("selected_party_id") val selectedPartyId: String,
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+ }
+
+ // Added in V1.11:
+
+ /**
+ * Matrix call SDP stream metadata changed event content
+ *
+ * @see matrix spec
+ */
+ @Serializable
+ data class SdpStreamMetadataChanged(
+ @EncodeDefault
+ @Serializable(with = VersionSerializer::class)
+ @SerialName("version") override val version: String = "1",
+ @SerialName("call_id") override val callId: String,
+ @SerialName("party_id") override val partyId: String,
+ @SerialName("sdp_stream_metadata") val sdpStreamMetadata: Map,
+ ) : CallEventContent {
+ override val relatesTo: RelatesTo? = null
+ override val mentions: Mentions? = null
+ override val externalUrl: String? = null
+ }
+}
+
+internal object VersionSerializer : KSerializer {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("net.folivo.trixnity.core.model.call.Version")
+
+ override fun deserialize(decoder: Decoder): String {
+ val jsonDecoder = decoder as? JsonDecoder ?: throw IllegalStateException("Expected JsonDecoder")
+ val element = jsonDecoder.decodeJsonElement().jsonPrimitive
+
+ // All VoIP events have a version field. This is used to determine whether devices support this new version of
+ // the protocol. For example, clients can use this field to know whether to expect an m.call.select_answer event
+ // from their opponent. If clients see events with version other than 0 or "1" (including, for example, the
+ // numeric value 1), they should treat these the same as if they had version == "1".
+ // @see matrix spec
+ return element.content
+ }
+
+ override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: String) {
+ when (value) {
+ "0" -> encoder.encodeLong(0)
+ else -> encoder.encodeString(value)
+ }
+ }
+}
diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/call/StreamMetadata.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/call/StreamMetadata.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b40561a16251243edd5ed55d59bc5f1a82ed9863
--- /dev/null
+++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/call/StreamMetadata.kt
@@ -0,0 +1,21 @@
+package net.folivo.trixnity.core.model.events.m.call
+
+import kotlinx.serialization.EncodeDefault
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+enum class Purpose {
+ @SerialName("m.usermedia") USERMEDIA,
+ @SerialName("m.screenshare") SCREENSHARE,
+}
+
+@Serializable
+data class StreamMetadata(
+ @SerialName("purpose") val purpose: Purpose,
+
+ // Added in v1.11:
+
+ @SerialName("audio_muted") val audioMuted: Boolean? = false,
+ @SerialName("video_muted") val videoMuted: Boolean? = false,
+)
diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/room/RoomMessageEventContent.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/room/RoomMessageEventContent.kt
index 4a19f600579e10d619ea878ddd5a7d5edbf9ae89..2a81464093d95727887a96aeb248cb4c25dbf87a 100644
--- a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/room/RoomMessageEventContent.kt
+++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/model/events/m/room/RoomMessageEventContent.kt
@@ -18,6 +18,9 @@ import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent.*
import net.folivo.trixnity.core.model.events.m.key.verification.VerificationRequest as IVerificationRequest
/**
+ * Matrix room message event content
+ *
+ * Room messages have "type": "m.room.message".
* @see matrix spec
*/
@Serializable(with = RoomMessageEventContentSerializer::class)
diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/serialization/events/DefaultEventContentSerializerMappings.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/serialization/events/DefaultEventContentSerializerMappings.kt
index 7d5baf81d58f76a12d158c69a202028080b587c4..18e3f6339c27fcb51a6deff02f9662806c584b1e 100644
--- a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/serialization/events/DefaultEventContentSerializerMappings.kt
+++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/serialization/events/DefaultEventContentSerializerMappings.kt
@@ -1,6 +1,7 @@
package net.folivo.trixnity.core.serialization.events
import net.folivo.trixnity.core.model.events.m.*
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent
import net.folivo.trixnity.core.model.events.m.crosssigning.MasterKeyEventContent
import net.folivo.trixnity.core.model.events.m.crosssigning.SelfSigningKeyEventContent
import net.folivo.trixnity.core.model.events.m.crosssigning.UserSigningKeyEventContent
@@ -28,6 +29,14 @@ val DefaultEventContentSerializerMappings = createEventContentSerializerMappings
messageOf("m.key.verification.accept")
messageOf("m.key.verification.key")
messageOf("m.key.verification.mac")
+ messageOf("m.call.invite")
+ messageOf("m.call.candidates")
+ messageOf("m.call.answer")
+ messageOf("m.call.hangup")
+ messageOf("m.call.negotiate")
+ messageOf("m.call.reject")
+ messageOf("m.call.select_answer")
+ messageOf("m.call.sdp_stream_metadata_changed")
stateOf("m.room.avatar")
stateOf("m.room.canonical_alias")
@@ -84,4 +93,4 @@ val DefaultEventContentSerializerMappings = createEventContentSerializerMappings
roomAccountDataOf("m.fully_read")
roomAccountDataOf("m.marked_unread")
roomAccountDataOf("m.tag")
-}
\ No newline at end of file
+}
diff --git a/trixnity-core/src/commonTest/kotlin/net/folivo/trixnity/core/serialization/m/call/CallEventContentSerializerTest.kt b/trixnity-core/src/commonTest/kotlin/net/folivo/trixnity/core/serialization/m/call/CallEventContentSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e4cda559aa8222918dadec7dab71270f457a1086
--- /dev/null
+++ b/trixnity-core/src/commonTest/kotlin/net/folivo/trixnity/core/serialization/m/call/CallEventContentSerializerTest.kt
@@ -0,0 +1,305 @@
+package net.folivo.trixnity.core.serialization.m.call
+
+import kotlinx.serialization.encodeToString
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Answer.Answer
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Answer.AnswerType
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Candidates.Candidate
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Invite.Offer
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Invite.OfferType
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Hangup.Reason
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Negotiate.Description
+import net.folivo.trixnity.core.model.events.m.call.CallEventContent.Negotiate.DescriptionType
+import net.folivo.trixnity.core.model.events.m.call.Purpose
+import net.folivo.trixnity.core.model.events.m.call.StreamMetadata
+import net.folivo.trixnity.core.serialization.createMatrixEventJson
+import net.folivo.trixnity.core.serialization.trimToFlatJson
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class CallEventContentSerializerTest {
+
+ private val json = createMatrixEventJson()
+
+ ////////////////// m.call.invite //////////////////
+
+ private val testInvite = CallEventContent.Invite(
+ version = "1",
+ callId = "0123",
+ offer = Offer(sdp = """
+ v=0
+ o=- 6584580628695956864 2 IN IP4 127.0.0.1
+ """.trimIndent(),
+ type = OfferType.OFFER),
+ lifetime = 30000,
+ sdpStreamMetadata = mapOf(
+ "271828182845" to StreamMetadata(Purpose.SCREENSHARE),
+ "314159265358" to StreamMetadata(Purpose.USERMEDIA),
+ )
+ )
+
+ private val serializedInvite = """{
+ "version": "1",
+ "call_id": "0123",
+ "lifetime": 30000,
+ "offer": {
+ "sdp": "v=0\no=- 6584580628695956864 2 IN IP4 127.0.0.1",
+ "type": "offer"
+ },
+ "sdp_stream_metadata": {
+ "271828182845": {
+ "purpose": "m.screenshare",
+ "audio_muted": false,
+ "video_muted": false
+ },
+ "314159265358": {
+ "purpose": "m.usermedia",
+ "audio_muted": false,
+ "video_muted": false
+ }
+ }
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallInvite() {
+ val result = json.encodeToString(testInvite)
+ assertEquals(serializedInvite, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallInvite() {
+ val result: CallEventContent.Invite = json.decodeFromString(serializedInvite)
+ assertEquals(testInvite, result)
+ }
+
+ ////////////////// m.call.candidates //////////////////
+
+ private val testCandidates = CallEventContent.Candidates(
+ version = "0",
+ callId = "0123",
+ candidates = listOf(
+ Candidate(
+ candidate = "candidate:423458654 1 udp 2130706431 192.168.0.100 51008 typ host generation 0 ufrag 3f8a",
+ sdpMid = "audio",
+ sdpMLineIndex = 0
+ ),
+ Candidate(
+ candidate = "candidate:423458654 2 udp 2130706431 192.168.0.100 51009 typ host generation 0 ufrag 4839",
+ sdpMid = "audio",
+ sdpMLineIndex = 1
+ ),
+ ),
+ )
+
+ private val serializedCandidates = """{
+ "version": 0,
+ "call_id": "0123",
+ "candidates": [
+ {"candidate":"candidate:423458654 1 udp 2130706431 192.168.0.100 51008 typ host generation 0 ufrag 3f8a","sdpMLineIndex":0,"sdpMid":"audio"},
+ {"candidate":"candidate:423458654 2 udp 2130706431 192.168.0.100 51009 typ host generation 0 ufrag 4839","sdpMLineIndex":1,"sdpMid":"audio"}
+ ]
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallCandidates() {
+ val result = json.encodeToString(testCandidates)
+ assertEquals(serializedCandidates, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallCandidates() {
+ val result: CallEventContent.Candidates = json.decodeFromString(serializedCandidates)
+ assertEquals(testCandidates, result)
+ }
+
+ ////////////////// m.call.answer //////////////////
+
+ private val testAnswer = CallEventContent.Answer(
+ version = "0",
+ callId = "0123",
+ partyId = null,
+ answer = Answer(sdp = "v=0", type = AnswerType.ANSWER),
+ sdpStreamMetadata = null,
+ )
+ private val serializedAnswer = """{
+ "version": 0,
+ "call_id": "0123",
+ "answer": {"sdp":"v=0","type":"answer"}
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallAnswer() {
+ val result = json.encodeToString(testAnswer)
+ assertEquals(serializedAnswer, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallAnswer() {
+ val result: CallEventContent.Answer = json.decodeFromString(serializedAnswer)
+ assertEquals(testAnswer, result)
+ }
+
+ ////////////////// m.call.hangup //////////////////
+
+ private val testHangup = CallEventContent.Hangup(
+ callId = "0123",
+ version = "0",
+ partyId = null,
+ reason = Reason.INVITE_TIMEOUT
+ )
+
+ private val serializedHangup = """{
+ "version": 0,
+ "call_id": "0123",
+ "reason": "invite_timeout"
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallHangup() {
+ val result = json.encodeToString(testHangup)
+ assertEquals(serializedHangup, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallHangup() {
+ val result: CallEventContent.Hangup = json.decodeFromString(serializedHangup)
+ assertEquals(testHangup, result)
+ }
+
+ ////////////////// m.call.negotiate //////////////////
+
+ private val testNegotiate = CallEventContent.Negotiate(
+ callId = "0123",
+ partyId = "67890",
+ description = Description("v=0", DescriptionType.OFFER),
+ lifetime = 10000,
+ sdpStreamMetadata = mapOf(
+ "271828182845" to StreamMetadata(Purpose.SCREENSHARE),
+ "314159265358" to StreamMetadata(Purpose.USERMEDIA),
+ ),
+ )
+ private val serializedNegotiate = """{
+ "version": "1",
+ "call_id": "0123",
+ "party_id": "67890",
+ "description": {
+ "sdp": "v=0",
+ "type": "offer"
+ },
+ "lifetime": 10000,
+ "sdp_stream_metadata": {
+ "271828182845": {
+ "purpose": "m.screenshare",
+ "audio_muted": false,
+ "video_muted": false
+ },
+ "314159265358": {
+ "purpose": "m.usermedia",
+ "audio_muted": false,
+ "video_muted": false
+ }
+ }
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallNegotiate() {
+ val result = json.encodeToString(testNegotiate)
+ assertEquals(serializedNegotiate, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallNegotiate() {
+ val result: CallEventContent.Negotiate = json.decodeFromString(serializedNegotiate)
+ assertEquals(testNegotiate, result)
+ }
+
+ ////////////////// m.call.reject //////////////////
+
+ private val testReject = CallEventContent.Reject(callId = "0123", partyId = "23423")
+
+ private val serializedReject = """{
+ "version": "1",
+ "call_id": "0123",
+ "party_id": "23423"
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallReject() {
+ val result = json.encodeToString(testReject)
+ assertEquals(serializedReject, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallReject() {
+ val result: CallEventContent.Reject = json.decodeFromString(serializedReject)
+ assertEquals(testReject, result)
+ }
+
+ ////////////////// m.call.select_answer //////////////////
+
+ private val testSelectAnswer = CallEventContent.SelectAnswer(
+ version = "0",
+ callId = "0123",
+ partyId = "23423",
+ selectedPartyId = "67890"
+ )
+
+ private val serializedSelectAnswer = """{
+ "version": 0,
+ "call_id": "0123",
+ "party_id": "23423",
+ "selected_party_id": "67890"
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldSerializeCallSelectAnswer() {
+ val result = json.encodeToString(testSelectAnswer)
+ assertEquals(serializedSelectAnswer, result)
+ }
+
+ @Test
+ fun shouldDeserializeCallSelectAnswer() {
+ val result: CallEventContent.SelectAnswer = json.decodeFromString(serializedSelectAnswer)
+ assertEquals(testSelectAnswer, result)
+ }
+
+ ////////////////// m.call.sdp_stream_metadata_changed //////////////////
+
+ private val testSdpStreamMetadataChanged = CallEventContent.SdpStreamMetadataChanged(
+ callId = "0123",
+ partyId = "67890",
+ sdpStreamMetadata = mapOf(
+ "271828182845" to StreamMetadata(Purpose.SCREENSHARE, audioMuted = true),
+ "314159265358" to StreamMetadata(Purpose.USERMEDIA, videoMuted = true),
+ ),
+ )
+ private val serializedSdpStreamMetadataChanged = """{
+ "version": "1",
+ "call_id": "0123",
+ "party_id": "67890",
+ "sdp_stream_metadata": {
+ "271828182845": {
+ "purpose": "m.screenshare",
+ "audio_muted": true,
+ "video_muted": false
+ },
+ "314159265358": {
+ "purpose": "m.usermedia",
+ "audio_muted": false,
+ "video_muted": true
+ }
+ }
+ }""".trimToFlatJson()
+
+ @Test
+ fun shouldDeserializeSdpStreamMetadataChanged() {
+ val result: CallEventContent.SdpStreamMetadataChanged = json.decodeFromString(serializedSdpStreamMetadataChanged)
+ assertEquals(testSdpStreamMetadataChanged, result)
+ }
+
+ @Test
+ fun shouldSerializeSdpStreamMetadataChanged() {
+ val result = json.encodeToString(testSdpStreamMetadataChanged)
+ assertEquals(serializedSdpStreamMetadataChanged, result)
+ }
+}