@@ -41,6 +41,7 @@ import io.livekit.android.room.isSVCCodec
4141import io.livekit.android.room.rpc.RpcClientManager
4242import io.livekit.android.room.rpc.RpcManager
4343import io.livekit.android.room.rpc.RpcServerManager
44+ import io.livekit.android.room.signal.SignalRequestException
4445import io.livekit.android.room.track.DataPublishReliability
4546import io.livekit.android.room.track.LocalAudioTrack
4647import io.livekit.android.room.track.LocalAudioTrackOptions
@@ -59,13 +60,19 @@ import io.livekit.android.room.track.screencapture.ScreenCaptureParams
5960import io.livekit.android.room.util.EncodingUtils
6061import io.livekit.android.rpc.RpcError
6162import io.livekit.android.util.LKLog
63+ import io.livekit.android.util.TimeoutException
6264import io.livekit.android.util.flow
6365import io.livekit.android.util.rethrowIfCancellationSignal
66+ import io.livekit.android.util.withDeadline
6467import io.livekit.android.webrtc.sortVideoCodecPreferences
68+ import kotlinx.coroutines.CancellationException
69+ import kotlinx.coroutines.CompletableDeferred
6570import kotlinx.coroutines.CoroutineDispatcher
6671import kotlinx.coroutines.Job
6772import kotlinx.coroutines.async
6873import kotlinx.coroutines.coroutineScope
74+ import kotlinx.coroutines.flow.combine
75+ import kotlinx.coroutines.flow.first
6976import kotlinx.coroutines.launch
7077import kotlinx.coroutines.sync.Mutex
7178import kotlinx.coroutines.sync.withLock
@@ -90,6 +97,7 @@ import javax.inject.Named
9097import kotlin.math.max
9198import kotlin.math.min
9299import kotlin.time.Duration
100+ import kotlin.time.Duration.Companion.milliseconds
93101
94102class LocalParticipant
95103@AssistedInject
@@ -130,6 +138,8 @@ internal constructor(
130138
131139 private val jobs = mutableMapOf<LocalTrackPublication , Job >()
132140
141+ private val pendingSignalRequests = Collections .synchronizedMap(mutableMapOf<Int , CompletableDeferred <Unit >>())
142+
133143 // For ensuring that only one caller can execute setTrackEnabled at a time.
134144 // Without it, there's a potential to create multiple of the same source,
135145 // Camera has deadlock issues with multiple CameraCapturers trying to activate/stop.
@@ -1109,12 +1119,59 @@ internal constructor(
11091119 }
11101120 }
11111121
1122+ /* *
1123+ * Sets and updates the metadata of the local participant.
1124+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
1125+ *
1126+ * @param metadata the metadata to set
1127+ * @return a [Result] that succeeds when the server confirms the update, or fails with
1128+ * [SignalRequestException] if the server rejects the request or
1129+ * [TimeoutException] if it times out
1130+ */
1131+ @CheckResult
1132+ suspend fun setMetadata (metadata : String ): Result <Unit > {
1133+ return requestMetadataUpdate(metadata = metadata)
1134+ }
1135+
1136+ /* *
1137+ * Sets and updates the name of the local participant.
1138+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
1139+ *
1140+ * @param name the name to set
1141+ * @return a [Result] that succeeds when the server confirms the update, or fails with
1142+ * [SignalRequestException] if the server rejects the request or
1143+ * [TimeoutException] if it times out
1144+ */
1145+ @CheckResult
1146+ suspend fun setName (name : String ): Result <Unit > {
1147+ return requestMetadataUpdate(name = name)
1148+ }
1149+
1150+ /* *
1151+ * Set or update participant attributes. It will make updates only to keys that
1152+ * are present in [attributes], and will not override others.
1153+ *
1154+ * To delete a value, set the value to an empty string.
1155+ *
1156+ * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
1157+ *
1158+ * @param attributes attributes to update
1159+ * @return a [Result] that succeeds when the server confirms the update, or fails with
1160+ * [SignalRequestException] if the server rejects the request or
1161+ * [TimeoutException] if it times out
1162+ */
1163+ @CheckResult
1164+ suspend fun setAttributes (attributes : Map <String , String >): Result <Unit > {
1165+ return requestMetadataUpdate(attributes = attributes)
1166+ }
1167+
11121168 /* *
11131169 * Updates the metadata of the local participant. Changes will not be reflected until the
11141170 * server responds confirming the update.
11151171 * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
11161172 * @param metadata
11171173 */
1174+ @Deprecated(message = " Use the suspend function setMetadata instead." )
11181175 fun updateMetadata (metadata : String ) {
11191176 this .engine.client.sendUpdateLocalMetadata(metadata, name)
11201177 }
@@ -1125,6 +1182,7 @@ internal constructor(
11251182 * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
11261183 * @param name
11271184 */
1185+ @Deprecated(message = " Use the suspend function setName instead." )
11281186 fun updateName (name : String ) {
11291187 this .engine.client.sendUpdateLocalMetadata(metadata, name)
11301188 }
@@ -1138,6 +1196,7 @@ internal constructor(
11381196 * Note: this requires `canUpdateOwnMetadata` permission.
11391197 * @param attributes attributes to update
11401198 */
1199+ @Deprecated(message = " Use the suspend function setAttributes instead." )
11411200 fun updateAttributes (attributes : Map <String , String >) {
11421201 this .engine.client.sendUpdateLocalMetadata(metadata, name, attributes)
11431202 }
@@ -1147,6 +1206,88 @@ internal constructor(
11471206 pub?.muted = muted
11481207 }
11491208
1209+ internal fun handleRequestResponse (response : LivekitRtc .RequestResponse ) {
1210+ val deferred = pendingSignalRequests[response.requestId] ? : return
1211+ if (response.reason != LivekitRtc .RequestResponse .Reason .OK ) {
1212+ pendingSignalRequests.remove(response.requestId)
1213+ deferred.completeExceptionally(SignalRequestException .fromResponse(response))
1214+ return
1215+ }
1216+ pendingSignalRequests.remove(response.requestId)
1217+ }
1218+
1219+ private suspend fun requestMetadataUpdate (
1220+ metadata : String? = null,
1221+ name : String? = null,
1222+ attributes : Map <String , String >? = null,
1223+ ): Result <Unit > {
1224+ val requestId = engine.client.allocateRequestId()
1225+ val deferred = CompletableDeferred <Unit >()
1226+ pendingSignalRequests[requestId] = deferred
1227+
1228+ return try {
1229+ engine.client.sendUpdateLocalMetadata(
1230+ metadata = metadata ? : this .metadata,
1231+ name = name ? : this .name,
1232+ attributes = attributes ? : emptyMap(),
1233+ requestId = requestId,
1234+ )
1235+ withDeadline(METADATA_UPDATE_TIMEOUT ) {
1236+ coroutineScope {
1237+ val confirmationJob = launch {
1238+ combine(
1239+ ::name.flow,
1240+ ::metadata.flow,
1241+ ::attributes.flow,
1242+ ) { _, _, _ -> }
1243+ .first { isMetadataUpdateConfirmed(metadata, name, attributes) }
1244+ if (! deferred.isCompleted) {
1245+ deferred.complete(Unit )
1246+ }
1247+ }
1248+ try {
1249+ deferred.await()
1250+ } finally {
1251+ confirmationJob.cancel()
1252+ }
1253+ }
1254+ }
1255+ Result .success(Unit )
1256+ } catch (e: TimeoutException ) {
1257+ deferred.completeExceptionally(e)
1258+ Result .failure(e)
1259+ } catch (e: CancellationException ) {
1260+ deferred.cancel()
1261+ throw e
1262+ } catch (e: Exception ) {
1263+ Result .failure(e)
1264+ } finally {
1265+ pendingSignalRequests.remove(requestId)
1266+ }
1267+ }
1268+
1269+ private fun isMetadataUpdateConfirmed (
1270+ metadata : String? ,
1271+ name : String? ,
1272+ attributes : Map <String , String >? ,
1273+ ): Boolean {
1274+ if (name != null && this .name != name) {
1275+ return false
1276+ }
1277+ if (metadata != null && this .metadata != metadata) {
1278+ return false
1279+ }
1280+ if (attributes != null &&
1281+ ! attributes.all { (key, value) ->
1282+ val current = this .attributes[key]
1283+ current == value || (value.isEmpty() && current.isNullOrEmpty())
1284+ }
1285+ ) {
1286+ return false
1287+ }
1288+ return true
1289+ }
1290+
11501291 internal fun handleSubscribedQualityUpdate (subscribedQualityUpdate : LivekitRtc .SubscribedQualityUpdate ) {
11511292 if (! dynacast) {
11521293 return
@@ -1319,6 +1460,9 @@ internal constructor(
13191460 * @suppress
13201461 */
13211462 fun cleanup () {
1463+ pendingSignalRequests.values.forEach { it.cancel() }
1464+ pendingSignalRequests.clear()
1465+
13221466 for (pub in trackPublications.values) {
13231467 val track = pub.track
13241468
@@ -1394,6 +1538,10 @@ internal constructor(
13941538 interface Factory {
13951539 fun create (dynacast : Boolean ): LocalParticipant
13961540 }
1541+
1542+ companion object {
1543+ private val METADATA_UPDATE_TIMEOUT = 5_000 .milliseconds
1544+ }
13971545}
13981546
13991547internal fun LocalParticipant.publishTracksInfo (): List <LivekitRtc .TrackPublishedResponse > {
0 commit comments