lunionmeet_flutter 0.2.0
lunionmeet_flutter: ^0.2.0 copied to clipboard
SDK Flutter de lunion.meet — appels audio et vidéo temps réel à intégrer dans votre application Flutter (iOS, Android, web ; desktop macOS/Linux). Se connecte au SFU str0m (WebRTC standard, sans media [...]
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:lunionmeet_flutter/lunionmeet_flutter.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'lunion.meet',
theme: ThemeData.dark(useMaterial3: true),
home: const JoinScreen(),
);
}
}
/// Saisie des paramètres renvoyés par votre serveur (access.url / room / token).
class JoinScreen extends StatefulWidget {
const JoinScreen({super.key});
@override
State<JoinScreen> createState() => _JoinScreenState();
}
class _JoinScreenState extends State<JoinScreen> {
final _url = TextEditingController(text: 'wss://meet.lunion-lab.com/sfu');
final _room = TextEditingController();
final _token = TextEditingController();
final _name = TextEditingController(text: 'Awa');
void _join() {
if (_room.text.trim().isEmpty) return;
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => CallScreen(
url: _url.text.trim(),
room: _room.text.trim(),
token: _token.text.trim(),
name: _name.text.trim().isEmpty ? 'Invité' : _name.text.trim(),
),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Rejoindre une réunion')),
body: Padding(
padding: const EdgeInsets.all(20),
child: ListView(
children: [
TextField(controller: _name, decoration: const InputDecoration(labelText: 'Votre nom')),
const SizedBox(height: 12),
TextField(controller: _url, decoration: const InputDecoration(labelText: 'URL du SFU (access.url)')),
const SizedBox(height: 12),
TextField(controller: _room, decoration: const InputDecoration(labelText: 'Salle (access.room)')),
const SizedBox(height: 12),
TextField(controller: _token, decoration: const InputDecoration(labelText: 'Jeton (access.token)')),
const SizedBox(height: 24),
FilledButton(onPressed: _join, child: const Text('Rejoindre')),
],
),
),
);
}
}
class CallScreen extends StatefulWidget {
const CallScreen({
super.key,
required this.url,
required this.room,
required this.token,
required this.name,
});
final String url;
final String room;
final String token;
final String name;
@override
State<CallScreen> createState() => _CallScreenState();
}
class _CallScreenState extends State<CallScreen> {
late final LunionRoom _room = LunionRoom(
sfuUrl: widget.url,
room: widget.room,
name: widget.name,
token: widget.token.isEmpty ? null : widget.token,
);
@override
void initState() {
super.initState();
_room.connect().catchError((_) {});
}
@override
void dispose() {
_room.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: _room,
builder: (context, _) {
final tiles = <Widget>[
_Tile(stream: _room.localStream, label: '${widget.name} (vous)', mirror: true),
for (final p in _room.participants) _Tile(stream: p.stream, label: p.name),
];
return Scaffold(
body: SafeArea(
child: _room.status == RoomStatus.connecting
? const Center(child: CircularProgressIndicator())
: GridView.count(
crossAxisCount: 2,
childAspectRatio: 3 / 4,
padding: const EdgeInsets.all(8),
mainAxisSpacing: 8,
crossAxisSpacing: 8,
children: tiles,
),
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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.redAccent),
onPressed: () {
_room.leave();
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
}
}
class _Tile extends StatelessWidget {
const _Tile({required this.stream, required this.label, this.mirror = false});
final dynamic stream;
final String label;
final bool mirror;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Stack(
fit: StackFit.expand,
children: [
Container(color: Colors.black26),
LunionVideoView(stream: stream, mirror: mirror),
Positioned(
left: 8,
bottom: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: Text(label, style: const TextStyle(fontSize: 12)),
),
),
],
),
);
}
}