fix(MAT-164): proactive key poll on screen share + faster DEC_FAILED recovery
When a video track is subscribed (screen share starts), Element Call rotates the E2EE key. Instead of waiting for DEC_FAILED, proactively poll the timeline for the new key (6x @ 500ms = 3s window). Also reduce DEC_FAILED threshold from 3→1 and cooldown from 5s→2s for faster recovery when the proactive poll misses the rotation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
29
voice.py
29
voice.py
@@ -709,6 +709,27 @@ class VoiceSession:
|
||||
track_source = getattr(pub, 'source', None) or "unknown"
|
||||
self._video_track = t
|
||||
logger.info("Video track stored from %s source=%s for on-demand vision", p.identity, track_source)
|
||||
# Screen share starts → Element Call rotates E2EE key.
|
||||
# Proactively poll timeline for the new key instead of waiting
|
||||
# for DEC_FAILED (MAT-164).
|
||||
async def _proactive_key_poll(pid=p.identity):
|
||||
pre_key = self._caller_key
|
||||
for attempt in range(6): # 6 × 500ms = 3s
|
||||
await asyncio.sleep(0.5)
|
||||
if self._caller_key != pre_key:
|
||||
logger.info("Proactive poll: key rotated via sync (attempt %d)", attempt + 1)
|
||||
return
|
||||
new_key = await self._fetch_encryption_key_http()
|
||||
if new_key and new_key != pre_key:
|
||||
logger.info("Proactive poll: got new key from timeline (attempt %d, %s)",
|
||||
attempt + 1, new_key.hex()[:8])
|
||||
self.on_encryption_key(
|
||||
self._caller_identity.split(":")[0] if self._caller_identity else "",
|
||||
self._caller_identity.split(":")[-1] if self._caller_identity else "",
|
||||
new_key, 0)
|
||||
return
|
||||
logger.info("Proactive poll: no key rotation after 3s")
|
||||
asyncio.ensure_future(_proactive_key_poll())
|
||||
if int(t.kind) in (1, 2) and e2ee_opts is not None: # audio + video tracks
|
||||
caller_id = p.identity
|
||||
track_type = "video" if int(t.kind) == 2 else "audio"
|
||||
@@ -754,8 +775,8 @@ class VoiceSession:
|
||||
now = time.monotonic()
|
||||
if int(state) == 3:
|
||||
_dec_failed_count[p_id] = _dec_failed_count.get(p_id, 0) + 1
|
||||
# After 3+ DEC_FAILED: re-fetch key from timeline (key may have rotated)
|
||||
if _dec_failed_count[p_id] >= 3 and not _refetch_in_progress:
|
||||
# After 1+ DEC_FAILED: re-fetch key from timeline (key may have rotated)
|
||||
if _dec_failed_count[p_id] >= 1 and not _refetch_in_progress:
|
||||
_refetch_in_progress = True
|
||||
_p_id_copy = p_id # capture for closure
|
||||
async def _refetch_key():
|
||||
@@ -781,9 +802,9 @@ class VoiceSession:
|
||||
finally:
|
||||
_refetch_in_progress = False
|
||||
asyncio.ensure_future(_refetch_key())
|
||||
# Cooldown: only re-key every 5s to avoid tight loops
|
||||
# Cooldown: only re-key every 2s to avoid tight loops
|
||||
last = _last_rekey_time.get(p_id, 0)
|
||||
if (now - last) < 5.0:
|
||||
if (now - last) < 2.0:
|
||||
return
|
||||
_last_rekey_time[p_id] = now
|
||||
if self._caller_all_keys:
|
||||
|
||||
Reference in New Issue
Block a user