import 'package:flutter/material.dart'; import 'package:flutter_client/store/config.dart'; import 'package:flutter_client/store/controllers.dart'; import 'package:livekit_client/livekit_client.dart' as livekit; import 'package:flutter_client/widgets/livekit_widgets/controls.dart'; import 'package:flutter_client/widgets/livekit_widgets/participant.dart'; import 'dart:math' as math; class SingleRoomPage extends StatefulWidget { const SingleRoomPage({Key? key}) : super(key: key); @override State createState() => _SingleRoomPageState(); } class _SingleRoomPageState extends State { livekit.Room? theLivekitRoom; List participants = []; livekit.EventsListener? roomListener; @override void initState() { super.initState(); () async { await connectLiveKit(); }(); } @override void dispose() { () async { // always dispose listener theLivekitRoom?.removeListener(_onRoomDidUpdate); await disconnectLiveKit(); await roomListener?.dispose(); await theLivekitRoom?.dispose(); }(); super.dispose(); } Future connectLiveKit() async { var options = const livekit.ConnectOptions( autoSubscribe: true, protocolVersion: livekit.ProtocolVersion.v6); var roomOptions = const livekit.RoomOptions( defaultVideoPublishOptions: livekit.VideoPublishOptions( simulcast: true, ), ); theLivekitRoom = await livekit.LiveKitClient.connect( LivekitConfig.url, variable_controller.access_token ?? '', connectOptions: options, roomOptions: roomOptions); theLivekitRoom?.addListener(_onRoomDidUpdate); roomListener = theLivekitRoom?.createListener(); try { // video will fail when running in ios simulator await theLivekitRoom?.localParticipant?.setCameraEnabled(true); } catch (e) { print('could not publish video: $e'); } await theLivekitRoom?.localParticipant?.setMicrophoneEnabled(true); print('Joined room: ${theLivekitRoom?.name ?? ""}'); } Future disconnectLiveKit() async { if (theLivekitRoom != null) { await theLivekitRoom?.disconnect(); theLivekitRoom = null; } } void _onRoomDidUpdate() { _sortParticipants(); } void _sortParticipants() { List participants = []; if (theLivekitRoom?.participants.isNotEmpty == true) { participants.addAll(theLivekitRoom!.participants.values); print('participants: ${participants.length}'); // sort speakers for the grid participants.sort((a, b) { // loudest speaker first if (a.isSpeaking && b.isSpeaking) { if (a.audioLevel > b.audioLevel) { return -1; } else { return 1; } } // last spoken at final aSpokeAt = a.lastSpokeAt?.millisecondsSinceEpoch ?? 0; final bSpokeAt = b.lastSpokeAt?.millisecondsSinceEpoch ?? 0; if (aSpokeAt != bSpokeAt) { return aSpokeAt > bSpokeAt ? -1 : 1; } // video on if (a.hasVideo != b.hasVideo) { return a.hasVideo ? -1 : 1; } // joinedAt return a.joinedAt.millisecondsSinceEpoch - b.joinedAt.millisecondsSinceEpoch; }); } if (theLivekitRoom != null) { final localParticipant = theLivekitRoom?.localParticipant; if (localParticipant != null) { if (participants.length > 1) { participants.insert(1, localParticipant); } else { participants.add(localParticipant); } } } setState(() { this.participants = participants; }); } // Future addVideoTrack() async { // if (theRoom == null) { // return; // } // var localVideo = await LocalVideoTrack.createCameraTrack(); // await theRoom?.localParticipant?.publishVideoTrack(localVideo); // } @override Widget build(BuildContext context) => Scaffold( body: Column( children: [ Expanded( child: participants.isNotEmpty ? ParticipantWidget.widgetFor(participants.first) : Container()), SizedBox( height: 100, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: math.max(0, participants.length - 1), itemBuilder: (BuildContext context, int index) => SizedBox( width: 100, height: 100, child: ParticipantWidget.widgetFor(participants[index + 1]), ), ), ), if (theLivekitRoom?.localParticipant != null) SafeArea( top: false, child: ControlsWidget( theLivekitRoom!, theLivekitRoom!.localParticipant!), ), ], ), ); }