feat(calls): add consent-based signaling to existing ChatHub
Wire shared PostIt call signaling, account-derived relay policy and temporary TURN credentials. Keep calls disabled pending native Android/Linux media integration. Document experimental Android crash and laboratory removal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
56ca115f50
commit
5e48831d6d
20 changed files with 1368 additions and 16 deletions
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
## Statut et périmètre
|
||||
|
||||
Ce document décrit un **plan d'implémentation** : les appels ne sont pas encore
|
||||
disponibles. La préférence de confidentialité est persistée et éditable dans
|
||||
le profil, mais ne pilote aucun transport média à ce stade.
|
||||
Ce document décrit l'architecture cible et l'implémentation commencée :
|
||||
les appels ne sont pas encore disponibles. La préférence de confidentialité
|
||||
est persistée et éditable dans le profil. Les premiers contrats de
|
||||
signalisation sont intégrés au chat, désactivés par défaut ; aucun transport
|
||||
média natif n'est encore raccordé.
|
||||
Il complète la [topologie de PostIt](postit.md) et les
|
||||
[mécanismes de notification existants](notifications.md).
|
||||
|
||||
œ
|
||||
La cible couvre :
|
||||
|
||||
- les appels audio-vidéo bidirectionnels entre deux utilisateurs ;
|
||||
|
|
@ -29,8 +31,11 @@ La cible couvre :
|
|||
| Appels de groupe | SFU compatible WebRTC | Réception et redistribution sélective des flux |
|
||||
|
||||
SignalR transporte les messages de contrôle, **jamais les flux audio-vidéo**.
|
||||
La signalisation peut utiliser l'infrastructure SignalR existante, mais les
|
||||
contrats d'appel et leurs autorisations doivent être distincts du chat.
|
||||
La signalisation doit réutiliser le [ChatHub existant](../../src/Yavsc.Server/Hubs/ChatHub.cs),
|
||||
déjà exposé par [Yavsc.Api](../../src/Yavsc.Api/Program.cs), et ses salles de
|
||||
discussion. Il n'est pas nécessaire de créer un second service de salles.
|
||||
Les contrats d'appel et leurs autorisations restent à définir explicitement :
|
||||
l'appartenance à une salle ne vaut pas acceptation d'un appel.
|
||||
Le choix des bibliothèques natives, du SFU et des codecs effectivement
|
||||
disponibles reste à valider par un prototype multi-plateforme.
|
||||
|
||||
|
|
@ -212,4 +217,140 @@ la signalisation des premiers appels à deux.
|
|||
- Activation du masquage pendant un appel : suspension des médias,
|
||||
remplacement vérifié du transport et retour d'état fidèle dans l'interface.
|
||||
- Échecs de permissions, de signalisation, de TURN et du SFU visibles,
|
||||
ressources caméra/micro et connexions libérées à la fin de l'appel.
|
||||
ressources caméra/micro et connexions libérées à la fin de l'appel.
|
||||
|
||||
## Première tranche intégrée : signalisation Android et Desktop Linux
|
||||
|
||||
Le périmètre initial retenu est **Android + Desktop Linux**. Il n'ajoute
|
||||
aucun serveur ni aucune salle : les méthodes suivantes appartiennent au
|
||||
[ChatHub](../../src/Yavsc.Server/Hubs/ChatHub.Calls.cs) existant.
|
||||
Les [contrats partagés](../../src/Yavsc.Abstract/Chat/Calls/CallContracts.cs)
|
||||
sont utilisés par le serveur et PostIt.
|
||||
|
||||
| Méthode | Effet |
|
||||
|---|---|
|
||||
| `InviteCall(roomName, callee)` | Invite un autre compte présent dans la salle et retourne un identifiant d'appel |
|
||||
| `AcceptCall(callId)` | Accepte sur un seul appareil, notifie les deux pairs et ferme l'invitation sur les autres appareils |
|
||||
| `SendCallSignal(callId, signal)` | Transmet l'offre, la réponse ou un candidat ICE uniquement à l'autre appareil accepté |
|
||||
| `EndCall(callId)` | Annule, refuse ou termine l'appel depuis un appareil autorisé |
|
||||
|
||||
Événements : `CallInvitation`, `CallAccepted`, `CallSignal(callId, signal)`
|
||||
et `CallEnded`. **Accepté ne veut pas dire connecté** : l'événement accepté
|
||||
exprime le consentement et fournit la politique ICE et les paramètres
|
||||
STUN/TURN, pas une réussite de l'échange média.
|
||||
|
||||
Le serveur vérifie les comptes, la présence dans la salle et le blocage
|
||||
entre utilisateurs. Les appareils d'un compte reçoivent l'invitation ;
|
||||
seul le premier qui accepte peut ensuite émettre la réponse et les
|
||||
candidats. L'appelant reste lié à sa connexion d'origine. Les médias
|
||||
ne sont jamais diffusés au groupe SignalR de la salle.
|
||||
|
||||
La préférence persistée est lue à l'invitation et relue à l'acceptation.
|
||||
Pour cette première version à deux, si l'un des comptes demande le
|
||||
masquage, **les deux transports doivent être relayés**, règle conservatrice
|
||||
plus stricte que la politique individuelle cible. L'acceptation ne peut
|
||||
pas diminuer la protection annoncée à l'invitation. Sans TURN configuré,
|
||||
un appel imposant le relais échoue explicitement.
|
||||
|
||||
La signalisation refuse les candidats non relayés, les adresses associées
|
||||
non masquées et les adresses directes dans les champs SDP contrôlés lorsque
|
||||
le relais est imposé. Ces contrôles de forme ne constituent pas un audit
|
||||
exhaustif de toutes les extensions SDP possibles : la non-divulgation doit
|
||||
encore être prouvée avec les SDP et statistiques des moteurs réellement
|
||||
intégrés. Aucun SDP n'est réécrit.
|
||||
|
||||
L'activation du masquage ou le changement de pseudo par l'API de profil
|
||||
termine l'appel existant ; le client devra libérer les médias et proposer
|
||||
un nouvel appel. La renégociation transparente du transport n'est pas
|
||||
implémentée. La déconnexion d'un pair sélectionné, le départ ou l'exclusion
|
||||
de la salle terminent également l'appel. Pas de reprise automatique après
|
||||
reconnexion : elle crée une nouvelle identité de connexion.
|
||||
|
||||
Les appels sont temporaires, en mémoire et **sur une seule instance API**.
|
||||
Une invitation expire après 45 secondes, une session acceptée après une
|
||||
heure ; les identifiants TURN expirent cinq minutes plus tard. Cette borne
|
||||
initiale ne remplace pas un futur renouvellement d'identifiants pour les
|
||||
appels longs. Un compte ne peut avoir qu'un appel réservé ou accepté à la
|
||||
fois. Le registre est limité à 128 appels et 256 candidats par appel.
|
||||
La tranche ne permet qu'une offre initiale de l'appelant et une réponse ;
|
||||
les redémarrages ICE et renégociations restent à implémenter.
|
||||
|
||||
Configuration serveur, section `AudioVideoCalls` :
|
||||
|
||||
- `Enabled` : `false` par défaut, à conserver tant que les moteurs ne sont
|
||||
pas validés ;
|
||||
- `StunUrls` : tableau d'URI `stun:` ou `stuns:` ;
|
||||
- `TurnUrls` : tableau d'URI `turn:` ou `turns:`, avec transport optionnel ;
|
||||
- `TurnSharedSecret` : secret REST coturn, **uniquement côté serveur**,
|
||||
à fournir par le gestionnaire de secrets ou la variable d'environnement
|
||||
`AudioVideoCalls__TurnSharedSecret`, jamais dans le dépôt.
|
||||
|
||||
Les credentials TURN temporaires sont calculés côté serveur avec le
|
||||
format REST coturn (expiration, nonce aléatoire et HMAC-SHA1). Aucun secret
|
||||
de longue durée n'est envoyé aux clients. Aucun service TURN n'a été
|
||||
déployé ou configuré par cette tranche.
|
||||
|
||||
Le [transport PostIt](../../src/PostIt/PostIt/Services/CallSignalingClient.cs)
|
||||
utilise la connexion authentifiée du `ChatViewModel`, sans seconde connexion
|
||||
ni interrogation HTTP. Il expose les commandes et événements typés et
|
||||
signale les interruptions ; il ne capture aucun média. L'interface ne
|
||||
propose pas encore de bouton d'appel ; une éventuelle invitation est
|
||||
signalée comme non utilisable faute de moteur natif, sans acceptation
|
||||
automatique.
|
||||
|
||||
### Travail natif restant avant activation
|
||||
|
||||
- Android : intégrer un adaptateur libwebrtc dans l'application existante,
|
||||
isoler le problème JNI observé, vérifier les versions Java/natives et
|
||||
conserver les diagnostics fatals utiles, sans journaliser SDP ou secrets.
|
||||
- Desktop Linux : intégrer un moteur WebRTC natif dans PostIt.Desktop
|
||||
avec capture réelle et rendu Avalonia ; un processus Python de test
|
||||
GStreamer ne vaut pas cette intégration.
|
||||
- Côté partagé : relier consentement, états réels du moteur, mise en
|
||||
sourdine, arrêt et libération des ressources à la signalisation.
|
||||
- Valider un appel Android ↔ Desktop Linux avec caméra avant et microphone
|
||||
réels, rendu et audio distants, VP8/Opus, puis transport TURN sélectionné
|
||||
dans les statistiques, sans repli direct lorsque le masquage est demandé.
|
||||
|
||||
Les tests de cette tranche portent sur les contrats, autorisations, courses
|
||||
d'acceptation, expirations, limites, interruption et préférence persistée.
|
||||
La compilation des applications Android et Desktop ne prouve pas le
|
||||
fonctionnement de WebRTC natif : le crash antérieur n'est pas résolu.
|
||||
|
||||
## Retour sur l'essai Android du 25 octobre 2026
|
||||
|
||||
Le laboratoire indépendant a été supprimé ; il ne constitue pas une base
|
||||
de salles de discussion ni une implémentation de PostIt. La suite doit
|
||||
réutiliser le ChatHub existant pour la signalisation.
|
||||
|
||||
L'essai en échec opposait un téléphone Android 15 arm64 à Chromium,
|
||||
en mode TURN uniquement, avec signalisation HTTP locale et TURN/TCP
|
||||
accessibles par redirections USB adb. Il n'utilisait ni ChatHub ni
|
||||
PostIt.Desktop Linux.
|
||||
|
||||
Les observations doivent être distinguées :
|
||||
|
||||
- Camera2, la capture audio et un premier rendu EGL local ont démarré.
|
||||
Cela ne valide ni le transport ni le rendu distant.
|
||||
- Le processus Android a subi un SIGABRT dans
|
||||
`libjingle_peerconnection_so.so`. Une première pile passait par
|
||||
`PeerConnection.nativeAddIceCandidate` ; après modification du traitement
|
||||
des candidats, un arrêt natif persistait sur `network_thread`.
|
||||
- Le test a attendu un échange média qui n'est jamais arrivé. Le dernier
|
||||
rapport Browser indiquait `connection: new`, zéro octet audio/vidéo et
|
||||
aucune paire ICE sélectionnée ; aucun rapport Android n'a validé l'appel.
|
||||
- Le problème a été reproduit avec les SDK libwebrtc 150.7871.04 et
|
||||
144.7559.15. L'attente des descriptions distantes, la résolution du
|
||||
`sdpMid` et l'ajout asynchrone des candidats n'ont pas suffi.
|
||||
|
||||
Il s'agit donc d'un **crash natif pendant la tentative d'établissement**,
|
||||
pas d'un refus de codec ou d'un échec TURN identifié. Le dernier appel JNI
|
||||
visible ne prouve pas la cause du crash. Sans message fatal détaillé ni
|
||||
pile native symbolisée, un défaut du code expérimental, de son intégration
|
||||
Java/JNI ou de libwebrtc reste à départager. Rien ne démontre une
|
||||
impossibilité de WebRTC sur Android ou une défaillance du ChatHub.
|
||||
|
||||
Les essais Browser/GStreamer réussis utilisaient des sources synthétiques
|
||||
et un décodage sans affichage côté GStreamer : ils ne valident pas
|
||||
PostIt.Desktop Linux, ses périphériques réels ou son rendu Avalonia.
|
||||
La validation multi-plateforme demeure incomplète.
|
||||
Loading…
Reference in a new issue