3f82bb5ff6
#3 JSON escaping: CallIceCandidateEvent.serializeCandidate() now escapes backslashes and quotes in SDP/sdpMid strings before interpolation. Prevents malformed JSON when SDP contains special characters. #4 Thread safety: remoteDescriptionSet changed from Boolean to AtomicBoolean to prevent race conditions between WebRTC callback threads and main thread accessing the flag simultaneously. #5 Async cleanup: hangup() no longer calls cleanup() synchronously after launching the coroutine. Cleanup is now triggered by the CallManager state collector when Ended state is reached, avoiding resource disposal during active WebRTC callbacks. #7 EglBase leak: createWebRtcSession() now wraps initialize() and createPeerConnection() in try-catch that disposes the session on failure, preventing EGL context leaks from failed initialization. #10 SDP failure: createOffer/createAnswer onCreateFailure() now calls onError() to surface SDP creation failures to the user instead of silently logging them. #11 Notification age: EventNotificationConsumer now uses CallOfferEvent.EXPIRATION_SECONDS (20s) instead of hardcoded 30s to align with the actual event expiration. https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS