fix: WebRTC call bugs, hardening, camera switch, and network resilience
Bug fixes: - Fix RemoteVideoMonitor killing group monitor job when primary track switches - Add mutex protection to CallManager.initiateCall() to prevent state races - Fix ICE restart offer never being sent to remote peer (was immediately replaced by a second offer from onRenegotiationNeeded) - Fix duplicate duration timer in PiP connected call UI - Fix error snackbar dismiss button not clearing the error - Make PeerSessionManager thread-safe with synchronized blocks (accessed from WebRTC native threads and coroutine dispatchers concurrently) - Make CallManager event handlers private (only called from onSignalingEvent) Improvements: - Replace fragile ICE candidate regex parsing with kotlinx.serialization JSON - Respect DND/silent mode: only ring in NORMAL mode, only vibrate in VIBRATE - Signal camera-off to remote peer by removing video track sender (instead of sending frozen/black frame) - Clear CallSessionBridge on AccountViewModel.onCleared() to prevent stale references on account switch - Custom TURN servers now replace defaults (instead of appending) so credentials can be rotated without an app update New features: - Front/back camera switch button (visible when video is enabled) - Network transition handling: ConnectivityManager.NetworkCallback triggers ICE restart on all peers when network changes (WiFi/cellular handoff) https://claude.ai/code/session_01JHn7skAibTrkVqsoWutgYe
This commit is contained in:
+19
-11
@@ -27,6 +27,10 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.pTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nipACWebRtcCalls.tags.callId
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
@Immutable
|
||||
class CallIceCandidateEvent(
|
||||
@@ -37,27 +41,31 @@ class CallIceCandidateEvent(
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : WebRTCEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
private val parsedJson by lazy {
|
||||
try {
|
||||
Json.parseToJsonElement(content).jsonObject
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun candidateJson() = content
|
||||
|
||||
fun candidateSdp(): String = CANDIDATE_REGEX.find(content)?.groupValues?.get(1) ?: ""
|
||||
fun candidateSdp(): String = parsedJson?.get("candidate")?.jsonPrimitive?.content ?: ""
|
||||
|
||||
fun sdpMid(): String = SDP_MID_REGEX.find(content)?.groupValues?.get(1) ?: "0"
|
||||
fun sdpMid(): String = parsedJson?.get("sdpMid")?.jsonPrimitive?.content ?: "0"
|
||||
|
||||
fun sdpMLineIndex(): Int =
|
||||
SDP_MLINE_INDEX_REGEX
|
||||
.find(content)
|
||||
?.groupValues
|
||||
?.get(1)
|
||||
?.toIntOrNull() ?: 0
|
||||
try {
|
||||
parsedJson?.get("sdpMLineIndex")?.jsonPrimitive?.int ?: 0
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 25052
|
||||
const val ALT_DESCRIPTION = "WebRTC ICE candidate"
|
||||
|
||||
private val CANDIDATE_REGEX = """"candidate"\s*:\s*"([^"]*)"""".toRegex()
|
||||
private val SDP_MID_REGEX = """"sdpMid"\s*:\s*"([^"]*)"""".toRegex()
|
||||
private val SDP_MLINE_INDEX_REGEX = """"sdpMLineIndex"\s*:\s*(\d+)""".toRegex()
|
||||
|
||||
fun serializeCandidate(
|
||||
sdp: String,
|
||||
sdpMid: String,
|
||||
|
||||
Reference in New Issue
Block a user