Date: 2026-03-14 Scope: 51 fichiers Swift, ~8 500 lignes (hors WebUI 42KB)
| Sévérité | Count | Domaines |
|---|---|---|
| Critical | 2 | Memory leak, dangling pointer |
| High | 10 | Thread safety (5), retain cycle (1), DRY (4) |
| Medium | 10 | God object, perf, resource mgmt, DRY |
| Low | 7 | Dead code, cleanup |
- Fichier:
StreamingCoordinator.swift:119-135 - Un
UnsafeMutablePointer<Int>est alloué pour le compteur de frames Flash mais jamais libéré. Fuite à chaque cycle start/stop. - Fix: Remplacer par une propriété class/actor, ou appeler
deallocate()dansstopStreaming().
- Fichier:
H264Encoder.swift:103-113 Unmanaged.passUnretained(self)passé comme refcon au callback VT. Si l'encoder est deallocé sans appelerstop(), le callback C déréférence un pointeur invalide. Pas dedeinitsafety net.- Fix: Ajouter un mécanisme qui appelle
stop()au deinit, ou utiliserpassRetainedavecreleasedansstop().
5 classes plain class avec état mutable accédé cross-thread sans synchronisation :
| ID | Fichier | État non protégé | Threads impliqués |
|---|---|---|---|
| H1 | NDIManager.swift |
isActive, ndiSender, ndiVideoFrame, frameCount |
capture thread + main |
| H2 | NDITallyPoller.swift |
lastProgram, lastPreview, currentTallyState |
polling Task + WebSocket Task + main actor |
| H3 | ThermalManager.swift |
isStreaming, currentLevel, warningIssued, criticalStopped |
TelemetryAggregator actor + main actor |
| H4 | RateLimitMiddleware.swift |
lastCameraUpdateTime |
NIO event loop threads |
| H5 | AuthMiddleware.swift |
isEnabled |
main thread (write) + NIO threads (read) |
Fix commun: Convertir en actors ou protéger avec OSAllocatedUnfairLock.
selfcapturé fortement dans closures NIO bootstrap + handlers (HTTPServerHandler,WebSocketServerHandler).- Cycle:
NetworkServer → bootstrap → closures → NetworkServer. - Fix:
[weak self]dans toutes les closures NIO, weak refs dans les handlers.
ServiceContainer.swiftvsAppCoordinator.swift:50-104— les deux créent le graphe complet de services. ServiceContainer semble dead code.- Fix: Supprimer
ServiceContainer.swiftou compléter la migration DI.
NetworkServer.swift:257-553contient des handlers complets,Controllers/en contient d'autres.- TODO Phase 5 dans le code mais jamais complété.
- Fix: Compléter la migration ou supprimer les controllers.
WebUI.swift(42KB) vsStaticController.swift(300 lignes inline HTML).- Fix: Supprimer
StaticController.swiftinline HTML (WebUI.swift est le code actif).
AVOCamError,CaptureError,NetworkErroravec cas identiques non consolidés.- Fix: Unifier sous
AVOCamErrorou supprimerAVOCamErroret garder les enums modulaires.
| ID | Fichier | Problème |
|---|---|---|
| M1 | AppCoordinator.swift (679 lignes) |
God Object — ~15 responsabilités, implémente NetworkRequestHandler (13 méthodes) |
| M2 | AppCoordinator.swift:157-182 |
stop() lance des Task fire-and-forget sans await — shutdown incomplet possible |
| M3 | NDIManager.swift:192-245 |
send_video_async_v2 + unlock pixel buffer immédiat = corruption possible |
| M4 | UDPTransmitter.swift:153-179 |
Await séquentiel par paquet UDP (30-40 awaits/frame 4K) — latence inutile |
| M5 | UDPTransmitter.swift:107-132 |
Polling loop 10ms pour attendre connection .ready au lieu de continuation |
| M6 | TelemetryCollector.swift:199-232 |
Leak Mach port rights — mach_port_deallocate jamais appelé (1/sec) |
| M7 | NDIManager.swift:271-273 |
mach_timebase_info() appelé à chaque frame au lieu d'être caché static |
| M8 | 4 fichiers | errorJSON() dupliqué 4 fois identiquement |
| M9 | AppCoordinator.swift + TelemetryAggregator.swift |
createDefaultTelemetry() dupliqué |
| M10 | 3 fichiers | Resolution parsing implémenté 3 fois différemment |
| ID | Fichier | Problème |
|---|---|---|
| S1 | BonjourService.swift:66 |
Bearer token broadcast en clair dans TXT record mDNS |
| S2 | BonjourService.swift:61-81 |
TXT record avec valeurs hardcodées ne reflétant pas la config réelle |
| ID | Fichier | Problème |
|---|---|---|
| L1 | ThermalMonitor.swift |
Doublon non utilisé de ThermalManager |
| L2 | SRTHelpers.swift |
Fonctions jamais appelées |
| L3 | UserDefaults+Keys.swift |
Accesseurs typés jamais utilisés |
| L4 | CaptureManager.swift:122-124 |
#available(iOS 10.0, *) toujours vrai |
| L5 | AppConfiguration.swift:99-109 |
Variable updated inutilisée |
| L6 | SRTManager.swift:121-163 |
Latency values calculées 2 fois |
| L7 | CaptureManager.swift:468-479 |
11 print() debug excessifs dans updateSettings() |
- Chaque phase est un commit atomique, testable
- Les agents parallèles ne touchent jamais les mêmes fichiers
- Vérification après chaque phase avant de passer à la suivante
Supprime le code mort pour réduire le bruit avant les vraies refactos.
Agent 0: Dead Code Cleanup
- Supprimer
ThermalMonitor.swift(L1) - Supprimer
SRTHelpers.swift(L2) - Supprimer
UserDefaults+Keys.swiftou câbler les accesseurs (L3) - Supprimer
ServiceContainer.swift(H7 — dead code DI) - Supprimer le HTML inline de
StaticController.swift(H9 — garder uniquement le routing si nécessaire) - Supprimer variable
updateddansAppConfiguration.swift(L5) - Supprimer
#available(iOS 10.0, *)guards obsolètes (L4) - Supprimer le double calcul latency dans
SRTManager.swift(L6)
Fichiers touchés: ThermalMonitor.swift, SRTHelpers.swift, UserDefaults+Keys.swift, ServiceContainer.swift, StaticController.swift, AppConfiguration.swift, CaptureManager.swift, SRTManager.swift
Vérification: make build-ios passe.
Corrige les 2 issues critiques. Fichiers disjoints, parallélisable.
Agent 1A: Fix Memory Leak — StreamingCoordinator.swift
- Scope: C1 uniquement
- Remplacer
UnsafeMutablePointer<Int>par une propriétéprivate var flashFrameCount: Int = 0 - Nettoyer l'allocation/capture dans la closure
- Fichiers:
StreamingCoordinator.swiftuniquement
Agent 1B: Fix Dangling Pointer — H264Encoder.swift
- Scope: C2 uniquement
- Ajouter un mécanisme de cleanup au deinit (task-local flag ou wrapper class avec deinit)
- S'assurer que
stop()invalide la session avant toute déallocation - Fichiers:
H264Encoder.swiftuniquement
Vérification: make build-ios passe.
Les 5 classes sont dans des domaines indépendants. Groupées par proximité fonctionnelle.
Agent 2A: Thread Safety NDI — NDIManager.swift + NDITallyPoller.swift
- Scope: H1, H2
- Convertir en actors OU ajouter
OSAllocatedUnfairLocksur l'état mutable - Fixer aussi M3 (
send_video_async_v2buffer unlock) et M7 (mach_timebase_infocaching) - Fichiers:
NDIManager.swift,NDITallyPoller.swift
Agent 2B: Thread Safety Middleware — RateLimitMiddleware.swift + AuthMiddleware.swift
- Scope: H4, H5
- Ajouter
OSAllocatedUnfairLock(actors non adaptés pour NIO handlers) - Fichiers:
RateLimitMiddleware.swift,AuthMiddleware.swift
Agent 2C: Thread Safety Thermal — ThermalManager.swift
- Scope: H3
- Convertir en actor ou ajouter lock
- Fichiers:
ThermalManager.swift
Vérification: make build-ios passe. Tester streaming NDI + Flash manuellement.
Agent 3A: Fix Retain Cycles NIO — NetworkServer.swift
- Scope: H6
- Passer toutes les closures NIO en
[weak self] HTTPServerHandleretWebSocketServerHandler: weak ref versserver- Fichiers:
NetworkServer.swift,HTTPServerHandler(si fichier séparé),WebSocketServerHandler(si fichier séparé)
Agent 3B: Fix Resource Leaks — TelemetryCollector.swift + UDPTransmitter.swift
- Scope: M6, M4, M5
TelemetryCollector: ajoutermach_port_deallocatepour chaque thread portUDPTransmitter.send(): fire-and-forget UDP au lieu d'await séquentielUDPTransmitter.connect(): remplacer polling loop par continuation propre- Fichiers:
TelemetryCollector.swift,UDPTransmitter.swift
Vérification: make build-ios passe. Test streaming Flash (latence améliorée attendue).
Agent 4A: Unifier Error Handling
- Scope: H10, M8
- Créer un
errorJSONunique dans un fichier utilitaire partagé (ex:Sources/Shared/Extensions/HTTPResponse+Error.swift) - Consolider les error enums : soit tout sous
AVOCamError, soit supprimerAVOCamErroret garder les enums modulaires - Fichiers:
AVOCamError.swift,NetworkServer.swift,HTTPRouter.swift,AuthMiddleware.swift,RateLimitMiddleware.swift
Agent 4B: Unifier Resolution Parsing + Default Telemetry
- Scope: M9, M10
- Créer
Resolutionstruct avecinit?(parsing:)dansSources/Shared/Models/ - Extraire
createDefaultTelemetry()enstatic funcsurTelemetry - Fichiers: nouveau
Resolution.swift,CaptureManager.swift,StreamingCoordinator.swift,SRTConfiguration.swift,AppCoordinator.swift,TelemetryAggregator.swift
Agent 4C: Cleanup Route Handlers Dupliqués
- Scope: H8
- Décider: garder
NetworkServerhandlers (code actif) et supprimer lesControllers/dupliqués, ou compléter la migration - Recommandation: supprimer les controllers dupliqués pour l'instant, la migration DI complète est un chantier séparé
- Fichiers:
Controllers/StatusController.swift,Controllers/StreamController.swift,Controllers/CameraController.swift,Controllers/SettingsController.swift
Vérification: make build-ios passe. Tester API endpoints.
Dépend des phases précédentes. Touche le fichier central.
Agent 5: Réduire AppCoordinator
- Scope: M1, M2
- Extraire
NetworkRequestHandlerconformance dans un adapter séparé (NetworkRequestAdapter.swift) - Rendre
stop()async et await tous les cleanup tasks - Réduire le logging excessif dans
CaptureManager.updateSettings()(L7 — ce fichier n'est pas touché par les phases précédentes) - Déplacer
isBatteryMonitoringEnabled = truedans leinit()deTelemetryCollector(M — pas touché en Phase 3B qui ne fait que port rights et ne modifie pas init) - Fichiers:
AppCoordinator.swift,NetworkRequestAdapter.swift(nouveau),CaptureManager.swift,TelemetryCollector.swift
Vérification: make build-ios passe. Test complet: démarrage app, streaming, arrêt propre.
Agent 6: Sécurité mDNS
- Scope: S1, S2
- Évaluer si le token dans le TXT record est intentionnel (discovery auto) — si oui, documenter
- Remplacer les valeurs hardcodées du TXT record par la config réelle du stream
- Unifier
createTXTRecord()etupdateFlashPort()(M — BonjourService DRY) - Fichiers:
BonjourService.swift
Vérification: make build-ios passe. Vérifier mDNS discovery depuis le Tauri controller.
Phase 0: [Agent 0 — Dead Code] ─────────────────────────────────────────┐
│
Phase 1: [Agent 1A — StreamingCoordinator] ──┐ │
[Agent 1B — H264Encoder] ──────────┤ (parallèle) │
│ │
Phase 2: [Agent 2A — NDI thread safety] ─────┐ │
[Agent 2B — Middleware safety] ─────┤ (parallèle) │
[Agent 2C — Thermal safety] ───────┘ │
│ │
Phase 3: [Agent 3A — NIO retain cycles] ────┐ │
[Agent 3B — Resource leaks] ───────┤ (parallèle) │
│ │
Phase 4: [Agent 4A — Error handling DRY] ───┐ │
[Agent 4B — Resolution/Telemetry] ─┤ (parallèle) │
[Agent 4C — Route handlers] ───────┘ │
│ │
Phase 5: [Agent 5 — AppCoordinator] ─────────┤ (séquentiel) │
│ │
Phase 6: [Agent 6 — Sécurité Bonjour] ──────┘ │
│
Total: 13 agents, 7 phases ┘
Vérifie qu'aucun fichier n'est touché par 2 agents dans la même phase :
| Phase | Agent | Fichiers modifiés |
|---|---|---|
| 0 | 0 | ThermalMonitor, SRTHelpers, UserDefaults+Keys, ServiceContainer, StaticController, AppConfiguration, CaptureManager, SRTManager |
| 1 | 1A | StreamingCoordinator |
| 1 | 1B | H264Encoder |
| 2 | 2A | NDIManager, NDITallyPoller |
| 2 | 2B | RateLimitMiddleware, AuthMiddleware |
| 2 | 2C | ThermalManager |
| 3 | 3A | NetworkServer, handlers NIO |
| 3 | 3B | TelemetryCollector, UDPTransmitter |
| 4 | 4A | AVOCamError, NetworkServer, HTTPRouter, AuthMiddleware, RateLimitMiddleware* |
| 4 | 4B | Resolution (new), CaptureManager, StreamingCoordinator, SRTConfiguration, AppCoordinator*, TelemetryAggregator |
| 4 | 4C | Controllers/* (suppression) |
| 5 | 5 | AppCoordinator, NetworkRequestAdapter (new), CaptureManager, TelemetryCollector* |
| 6 | 6 | BonjourService |
* Fichiers touchés dans des phases différentes — OK car les phases sont séquentielles.
Aucun conflit intra-phase.
-
make build-iospasse après chaque phase - 0
UnsafeMutablePointernon deallocated - 0 classe non-actor avec état mutable cross-thread non protégé
- 0 duplication
errorJSON - 0 fichier dead code restant
-
AppCoordinator< 400 lignes — SKIPPED: extraction NetworkRequestHandler trop couplée (voir Phase 5) - Streaming NDI, SRT, Flash fonctionnels après refacto complète — À tester manuellement
Toutes les phases exécutées avec succès. Build iOS OK après chaque phase.
| Phase | Status | Agents | Notes |
|---|---|---|---|
| 0 | ✅ Done | 1 | 4 fichiers supprimés, 3 nettoyés. UserDefaults+Keys.swift gardé (utilisé). |
| 1 | ✅ Done | 2 parallel | C1: FrameCounter class remplace UnsafeMutablePointer. C2: passRetained/release + deinit safety net. |
| 2 | ✅ Done | 3 parallel | 5 classes protégées par OSAllocatedUnfairLock. NDI: sync send + cached timebase. |
| 3 | ✅ Done | 2 parallel | NIO: [weak self] + weak server refs. UDP: fire-and-forget sends + continuation connect. Mach ports: deallocated. |
| 4 | ✅ Done | 3 parallel | errorJSON unifié via HTTPResponse+Convenience. Resolution struct partagé. Telemetry.makeDefault(). 5 controllers + APIController supprimés. AVOCamError gardé (utilisé). |
| 5 | ✅ Done | 1 | stop() async. Logging condensé. Battery monitoring init. M1 (extract handler) skipped — trop couplé. |
| 6 | ✅ Done | 1 | TXT record DRY. Token documenté comme intentionnel. Hardcoded values annotées TODO. |
ThermalMonitor.swift,SRTHelpers.swift,ServiceContainer.swiftStaticController.swift,StatusController.swift,StreamController.swiftCameraController.swift,SettingsController.swift,APIController.swift- Répertoires vides:
DI/,Controllers/
Sources/Shared/Models/Resolution.swift
- M1: Extraire
NetworkRequestHandlerdeAppCoordinator— nécessite un refacto plus profond du state management - H10: Unifier les error enums —
AVOCamErrorest utilisé, consolidation à planifier séparément - S2: Câbler
BonjourService.updateStreamInfo()depuisAppCoordinatorau démarrage du stream - Test manuel: Valider streaming NDI, SRT, Flash après refacto