Skip to main content

Kotlin Multiplatform Secure Room Operations

Build a complete MediaSFU room lifecycle with com.mediasfu:mediasfu-sdk:1.0.5: create or join through your backend, show participants, control microphone, camera, and screen sharing, observe remote media, and leave cleanly.

This guide uses the supplied MediasfuGeneric room and its MediasfuGenericState. It never puts a reusable MediaSFU credential in the application.

Before you start

Add the exact dependency to commonMain through the artifact source approved for your application, and confirm that Gradle resolves 1.0.5:

kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.mediasfu:mediasfu-sdk:1.0.5")
}
}
}

Your application also needs:

  • an authenticated HTTPS backend that creates and joins rooms for the signed-in user;
  • Android camera and microphone permissions before requesting those devices;
  • NSCameraUsageDescription and NSMicrophoneUsageDescription in every iOS target that uses them;
  • a real-device release test for camera, microphone, audio routing, backgrounding, and display capture on every platform you ship.

The backend decides which users may create or join, applies room type, duration and capacity limits, rate-limits requests, and keeps reusable MediaSFU credentials in server-side storage. Start with the secure backend pattern.

Inject secure create and join functions

MediasfuGenericOptions accepts createMediaSFURoom and joinMediaSFURoom. The SDK supplies a typed request to each callback. Forward only request.payload to your own backend:

import com.mediasfu.sdk.methods.utils.CreateJoinRoomResult
import com.mediasfu.sdk.methods.utils.CreateMediaSFUOptions
import com.mediasfu.sdk.methods.utils.CreateMediaSFURoomOptions
import com.mediasfu.sdk.methods.utils.JoinMediaSFUOptions
import com.mediasfu.sdk.methods.utils.JoinMediaSFURoomOptions
import com.mediasfu.sdk.ui.mediasfu.MediasfuGenericOptions

interface RoomBackend {
suspend fun create(payload: CreateMediaSFURoomOptions): CreateJoinRoomResult
suspend fun join(payload: JoinMediaSFURoomOptions): CreateJoinRoomResult
}

class SecureRoomGateway(private val backend: RoomBackend) {
suspend fun create(request: CreateMediaSFUOptions): CreateJoinRoomResult =
backend.create(request.payload)

suspend fun join(request: JoinMediaSFUOptions): CreateJoinRoomResult =
backend.join(request.payload)
}

fun secureRoomOptions(backend: RoomBackend): MediasfuGenericOptions {
val gateway = SecureRoomGateway(backend)
return MediasfuGenericOptions(
credentials = null,
createMediaSFURoom = gateway::create,
joinMediaSFURoom = gateway::join,
)
}

Do not forward request.apiKey or request.apiUserName. Do not put those values in source, Gradle properties packaged with the app, platform resources, logs, or crash reports.

Your RoomBackend implementation authenticates the current app session, calls your create or join endpoint, and maps the response to CreateJoinRoomResult. Return a successful CreateJoinRoomResponse in data when access is granted, or a CreateJoinRoomError when it is denied. Treat an expired app session as a new authorization request; do not replay an old room response.

Render the supplied room with the same options and state instance:

@Composable
fun KotlinRoom(backend: RoomBackend) {
val options = remember(backend) { secureRoomOptions(backend) }
val state = rememberMediasfuGenericState(options)
MediasfuGeneric(options = options, state = state)
}

The supplied pre-join screen calls the create or join adapter, then completes the socket-level room join. The room is authorized when state.validated.value becomes true. A successful backend response by itself is not proof that the socket joined.

For a denied request, show the returned application message and let the user correct the room ID or sign in again. For a timeout or network loss, keep the pre-join screen available and retry only after connectivity returns. Never silently fall back to a client credential.

Read participants and media state

Use one MediasfuGenericState as the source for your custom labels and controls:

data class RoomSnapshot(
val authorized: Boolean,
val devicePrepared: Boolean,
val participantCount: Int,
val microphoneActive: Boolean,
val cameraActive: Boolean,
val screenShareActive: Boolean,
val remoteMediaCount: Int,
)

fun snapshotRoom(state: MediasfuGenericState) = RoomSnapshot(
authorized = state.validated.value,
devicePrepared = state.connectivity.device != null,
participantCount = state.room.participants.size,
microphoneActive = state.media.audioAlreadyOn,
cameraActive = state.media.videoAlreadyOn,
screenShareActive = state.media.screenAlreadyOn || state.media.shareScreenStarted,
remoteMediaCount = state.streams.currentStreams.size +
state.streams.allAudioStreams.size + state.streams.remoteScreenStreams.size,
)

Render participant names from state.room.participants; the list updates as the room receives membership events. Keep the observations separate:

  • validated means the room session is authorized.
  • A non-null connectivity.device means the WebRTC device was prepared.
  • audioAlreadyOn and videoAlreadyOn describe local producer state.
  • currentStreams, allAudioStreams, and remoteScreenStreams describe media known to the consumer path.

Do not label the room “media ready” from permission alone. In a two-device test, require the sender's producer flag and observable playback on the receiver.

Toggle microphone, camera, and screen sharing

Use the state methods so the supplied room owns permission prompts, producer setup, and UI alerts:

fun toggleMicrophone(state: MediasfuGenericState) = state.toggleAudio()
fun toggleCamera(state: MediasfuGenericState) = state.toggleVideo()
fun toggleScreenShare(state: MediasfuGenericState) = state.toggleScreenShare()

After a successful microphone or camera action, the matching audioAlreadyOn or videoAlreadyOn flag changes. After screen capture starts, screenAlreadyOn or shareScreenStarted changes; call toggleScreenShare() again to stop it.

If permission is denied, keep the person in the room, name the missing permission, and offer a retry after the operating-system setting changes. If display capture is cancelled, keep microphone and camera controls available and leave the screen state inactive. If another device cannot consume the stream, show a reconnect action instead of claiming the media is live.

Leave and clean up

Participant leave is:

state.exitSession()

Wait for state.validated to become false, then remove your room route, stop app-owned timers or observers, clear cached room identifiers and discard the previous backend response. A new entry must obtain fresh backend authorization. On another connected device, confirm that the departing participant disappears from state.room.participants.

The 1.0.5 state provides semantic host-end behavior through exitSession(endRoomOnHostExit: true) and a host leave-without-ending path through exitSession(endRoomOnHostExit: false). Label these controls separately, show them only for the host, and clear app-owned state after the operation. The SDK operation is a role-aware request; verify the resulting room state on a second connected device before presenting the room as ended or preserved.

What success looks like

OperationObservable success
Create or joinvalidated changes to true after backend authorization and socket join.
Participant listBoth devices show the other participant in room.participants.
Microphone or cameraThe local producer flag changes and the second device receives the media.
Consume mediaThe receiving state gains the expected stream and playback is audible or visible.
Screen shareThe sharing state changes and the second device displays the selected surface; stopping reverses both.
Participant leavevalidated changes to false, local state is cleared, and the other device removes the participant.

Release checklist

  • Gradle resolves com.mediasfu:mediasfu-sdk:1.0.5 from your approved source.
  • Create and join use the authenticated backend adapters and the shipped app contains no reusable MediaSFU credential.
  • Denied authorization, expired sessions, invalid room IDs, and network loss have useful recovery paths.
  • Camera and microphone permissions work on every shipping target and denial leaves the room usable.
  • Two physical devices prove participant updates, microphone, camera, and remote consumption.
  • Screen sharing starts, cancels, stops, and recovers on every platform where you expose it.
  • Participant leave clears app-owned state and obtains fresh authorization on re-entry.
  • Host end and host leave-without-ending are labelled separately and verified on a second connected device.

The companion example contains the complete gateway, state snapshot, controls, and room wrapper. Its local test checks payload-only forwarding and operation-state boundaries without a network, room, or device.