startScreenCapture method

Future<void> startScreenCapture()

Implementation

Future<void> startScreenCapture() async {
  try {
    // Marcar actividad (inicio explícito)
    _markActivity();

    // Verificar si ya está corriendo el servicio
    final isRunning = await NativeBridge.isScreenCaptureRunning();
    if (!isRunning) {
      // Solicitar permiso de MediaProjection si no está activo
      final granted = await NativeBridge.requestMediaProjection();
      if (!granted) {
        log('❌ Permiso de captura de pantalla denegado');
        return;
      }

      // Esperar un momento para que el servicio inicie
      await Future.delayed(const Duration(milliseconds: 500));
    }

    // Iniciar servicio foreground
    final started = await NativeBridge.startScreenCapture();
    if (!started) {
      log('❌ No se pudo iniciar el servicio de captura');
      return;
    }

    log('✅ Servicio de captura iniciado');

    // 1. Crear PeerConnection PRIMERO con TURN server propio
    if (_peerConnection == null) {
      await testTurnConnectivity(turnServerIP ?? '', turnServerPort ?? 0);
      _peerConnection = await createPeerConnection(
        {
          'iceServers': [
            if (turnServerIP != null && turnServerPort != null)
              {
                'urls': 'turn:$turnServerIP:$turnServerPort',
                if (turnServerUsername != null) 'username': turnServerUsername,
                if (turnServerCredential != null) 'credential': turnServerCredential,
              },
            // TURNS (TLS) - opcional para certificados SSL
            // {
            //   'urls': 'turns:192.168.28.132:5349',
            //   'username': 'remotecontrol',
            //   'credential': 'remotecontrol123',
            // },
            // STUN servers públicos (gratuitos)
            {'urls': 'stun:stun.l.google.com:19302'},
            {'urls': 'stun:stun1.l.google.com:19302'},
          ],
          'iceTransportPolicy': 'all', // Permite STUN y TURN
          // 'iceTransportPolicy': 'relay', // Forzar uso de TURN para debugging
          'bundlePolicy': 'max-bundle',
          'rtcpMuxPolicy': 'require',
          'sdpSemantics': 'unified-plan',
        },
        {
          'mandatory': {},
          'optional': [
            {'DtlsSrtpKeyAgreement': true},
          ],
        },
      );

      // Configurar handlers de ICE candidates
      _peerConnection!.onIceCandidate = (candidate) {
        if (candidate.candidate == null || candidate.candidate!.isEmpty) {
          log('🧊 ICE: Candidato vacío (fin de gathering)');
          return;
        }

        // Marcar actividad cuando lleguen candidatos
        _markActivity();

        final candStr = candidate.candidate!;

        // 🔍 Detectar tipo de candidato
        String icon = '⚪';
        String type = 'UNKNOWN';

        if (candStr.contains('typ relay')) {
          icon = '🔵';
          type = 'TURN (relay)';
          // Extraer IP del relay
          final relayMatch = RegExp(r'raddr (\S+)').firstMatch(candStr);
          if (relayMatch != null) {
            // se puede usar relayMatch.group(1) para debug si se necesita
          }
        } else if (candStr.contains('typ srflx')) {
          icon = '🟡';
          type = 'STUN (srflx)';
        } else if (candStr.contains('typ host')) {
          icon = '🟢';
          type = 'HOST (local)';
        }

        // Log completo para debugging
        final display = candStr.length > 80 ? '${candStr.substring(0, 80)}...' : candStr;

        log('$icon ICE [$type]: $display');

        _sendMessage({'type': 'ice-candidate', 'candidate': candidate.toMap()});
      };

      // Configurar handler de estado de conexión
      _peerConnection!.onConnectionState = (state) {
        log('🔗 Estado de conexión WebRTC: $state');
        if (state == RTCPeerConnectionState.RTCPeerConnectionStateFailed) {
          log('❌ Conexión WebRTC falló - intentando limpiar recursos');
          _handleConnectionFailure();
        } else if (state == RTCPeerConnectionState.RTCPeerConnectionStateConnected) {
          log('✅ Conexión WebRTC establecida exitosamente');
          _markActivity();
        }
      };

      // Handler de estado de ICE
      _peerConnection!.onIceConnectionState = (state) {
        log('🧊 Estado ICE: $state');
        if (state == RTCIceConnectionState.RTCIceConnectionStateFailed || state == RTCIceConnectionState.RTCIceConnectionStateDisconnected) {
          log('⚠️ ICE connection issue: $state');
        }
      };

      // Handler de recolección de ICE
      _peerConnection!.onIceGatheringState = (state) {
        log('🧊 Estado de recolección ICE: $state');
        _markActivity();
      };

      log('✅ PeerConnection creado con configuración mejorada');
    }

    // 2. Capturar pantalla para WebRTC
    _localStream = await navigator.mediaDevices.getDisplayMedia({
      'video': {'width': 1280, 'height': 720, 'frameRate': 30},
      'audio': false,
    });

    log('✅ Stream de pantalla capturado');

    // 3. Agregar stream local al PeerConnection
    _localStream!.getTracks().forEach((track) {
      log('➕ Agregando track: ${track.kind} - ${track.id}');
      _peerConnection!.addTrack(track, _localStream!);
    });

    log('✅ Tracks agregados al PeerConnection');

    // 4. Crear oferta DESPUÉS de agregar tracks
    RTCSessionDescription offer = await _peerConnection!.createOffer({'offerToReceiveAudio': false, 'offerToReceiveVideo': true});
    await _peerConnection!.setLocalDescription(offer);

    log('✅ Oferta WebRTC creada');

    // 5. Enviar oferta al servidor
    _sendMessage({'type': 'webrtc-offer', 'sdp': offer.toMap()});

    // Marcar actividad tras generar y enviar oferta
    _markActivity();

    log('✅ Oferta enviada al servidor');
    log('✅ Screen capture y WebRTC iniciados completamente');

    // 6. Timeout de conexión - si no se conecta en 60 segundos, limpiar
    Future.delayed(const Duration(seconds: 60), () {
      if (_peerConnection != null && _peerConnection!.connectionState != RTCPeerConnectionState.RTCPeerConnectionStateConnected) {
        log('⏱️ Timeout de conexión WebRTC - limpiando recursos');
        log(jsonEncode(_peerConnection!.connectionState));
        _handleConnectionFailure();
      }
    });
  } catch (e) {
    log('❌ Error al iniciar screen capture: $e');
    // Limpiar en caso de error
    await stopScreenCapture();
  }
}