@lunionlab/meet-flutter (lunionmeet_flutter)

SDK Flutter de lunion.meet : ajoutez des appels audio et vidéo temps réel à votre application Flutter (iOS, Android, web ; desktop macOS/Linux) en quelques lignes.

Comme sur le web, le principe est en deux temps :

  1. Votre serveur crée une salle et émet un jeton de connexion avec @lunionlab/meet-server-sdk (createRoom puis createToken).
  2. Votre app Flutter rejoint la salle avec ce jeton via LunionRoom.

Le jeton est toujours émis côté serveur : votre clé d'API ne quitte jamais votre backend.

Installation

# pubspec.yaml
dependencies:
  lunionmeet_flutter: ^1.2.0

1.2.0 : RTCPeerConnection standard via flutter_webrtc (^0.12). Android, iOS, Windows et desktop sont pris en charge (voir Notes).

Permissions

Android : android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />

iOS : ios/Runner/Info.plist

<key>NSCameraUsageDescription</key>
<string>Pour les appels vidéo</string>
<key>NSMicrophoneUsageDescription</key>
<string>Pour les appels audio</string>

Desktop (Windows, macOS, Linux) : sur Windows et Linux, rien à déclarer. Sur macOS, ajoutez les entitlements caméra et micro dans macos/Runner/*.entitlements :

<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>

Utilisation

import 'package:flutter/material.dart';
import 'package:lunionmeet_flutter/lunionmeet_flutter.dart';

class CallScreen extends StatefulWidget {
  const CallScreen({super.key, required this.url, required this.room, required this.token});
  final String url;   // access.url   (renvoyé par votre serveur)
  final String room;  // access.room
  final String token; // access.token

  @override
  State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
  late final LunionRoom _room = LunionRoom(
    sfuUrl: widget.url,
    room: widget.room,
    name: 'Awa',
    token: widget.token,
  );

  @override
  void initState() {
    super.initState();
    _room.connect();
  }

  @override
  void dispose() {
    _room.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: _room,
      builder: (context, _) => Scaffold(
        body: GridView.count(
          crossAxisCount: 2,
          children: [
            LunionVideoView(stream: _room.localStream, mirror: true),
            for (final p in _room.participants)
              LunionVideoView(stream: p.stream),
          ],
        ),
        bottomNavigationBar: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              icon: Icon(_room.micEnabled ? Icons.mic : Icons.mic_off),
              onPressed: _room.toggleMic,
            ),
            IconButton(
              icon: Icon(_room.cameraEnabled ? Icons.videocam : Icons.videocam_off),
              onPressed: _room.toggleCamera,
            ),
            IconButton(
              icon: const Icon(Icons.call_end, color: Colors.red),
              onPressed: () => _room.leave(),
            ),
          ],
        ),
      ),
    );
  }
}

Récupérer un jeton (côté serveur, Node)

import { RoomServiceClient } from "@lunionlab/meet-server-sdk";

const rooms = new RoomServiceClient("https://meet.lunion-lab.com/api/v1", process.env.LUNION_API_KEY!);
const room = await rooms.createRoom("Réunion produit");
const access = await rooms.createToken(room.slug, "user-42", { name: "Awa" });
// → renvoyez access.url, access.room, access.token à votre app Flutter

Chiffrement de bout en bout (E2EE)

Chiffrez le média par frame (AES-GCM) avec une clé partagée uniquement entre les pairs : le SFU relaie de l'opaque, la clé ne transite jamais par le serveur. Établissez-la hors-bande (fragment d'URL, QR…) et donnez la même clé à tous les participants.

final key = LunionE2ee.fromBase64url('Vk9...'); // 256 bits, partagés hors-bande
final room = LunionRoom(
  sfuUrl: url, room: room, name: 'Awa', token: token,
  e2ee: key,
);

Repose sur le FrameCryptor natif de flutter_webrtc. À valider sur appareil (tous les pairs doivent partager la clé). Le SDK React Native ne propose pas encore l'E2EE (limite de react-native-webrtc standard).

Nouveautés 1.2

Partage d'écran fiabilisé : stopScreenShare() libère réellement la capture (ReplayKit/MediaProjection) et l'arrêt depuis le bandeau système est capté automatiquement ; leave() et la reconnexion réseau ne laissent plus de capture orpheline.

Rappel 1.1 : reconnexion réseau auto (reconnect) · réception de la modération (autoApplyModeration, onModerated) · locuteur actif (activeSpeaker) · enregistrement (startRecording/stopRecording, onRecordingStatus) · mode récepteur pur (receiveOnly) · bascule caméra (switchCamera) · qualité par couche (setVideoLayerPolicy) · E2EE (e2ee). Détails : CHANGELOG.

API

Élément Rôle
LunionRoom Contrôleur de salle (ChangeNotifier) : connect(), localStream, participants, toggleMic(), toggleCamera(), switchCamera(), shareScreen()/stopScreenShare()/isScreenSharing, sendChat(), leave().
RemoteParticipant id, name, stream (flux audio + vidéo).
RoomStatus idle · connecting · connected · reconnecting · error (via room.status).
LunionE2ee · LunionReconnectConfig · LunionVideoLayer Configs : E2EE, reconnexion, qualité par couche.
LunionVideoView Widget d'affichage d'un MediaStream.
LunionSignaling Client de signalisation bas niveau (usage avancé : modération, enregistrement…).

Partage d'écran

shareScreen() / stopScreenShare() fonctionnent tels quels sur desktop et web. Sur Android/iOS, le partage exige une configuration native de l'app hôte (MediaProjection + service au premier plan ; Broadcast Upload Extension + App Group). Guide complet : docs/SCREEN-SHARE-NATIVE.md — et des fichiers prêts à copier (manifeste Android, SampleHandler.swift, Info.plist) dans docs/screen-share-native/.

L'arrêt depuis le bandeau système est capté automatiquement (track.onEndedstopScreenShare()), et depuis la 1.3.0 le partage survit à une coupure réseau (capture ré-attachée et ré-étiquetée, sans nouvelle demande de consentement).

Notes

  • Repose sur flutter_webrtc (^0.12) en RTCPeerConnection standard.
  • Le protocole de signalisation est identique à celui du SDK web @lunionlab/meet-client-js.
  • Compilez et testez dans un projet Flutter réel (caméra/micro requis).

Libraries

lunionmeet_flutter
SDK Flutter de lunion.meet — appels audio et vidéo temps réel sur le SFU (RTCPeerConnection standard via flutter_webrtc).