import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:livekit_example/widgets/text_field.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../exts.dart'; import 'room.dart'; class ConnectPage extends StatefulWidget { // const ConnectPage({ Key? key, }) : super(key: key); @override State createState() => _ConnectPageState(); } class _ConnectPageState extends State { // static const _storeKeyUri = 'uri'; static const _storeKeyToken = 'token'; static const _storeKeySimulcast = 'simulcast'; static const _storeKeyAdaptiveStream = 'adaptive-stream'; static const _storeKeyDynacast = 'dynacast'; static const _storeKeyFastConnect = 'fast-connect'; static const _storeKeyE2EE = 'e2ee'; static const _storeKeySharedKey = 'shared-key'; final _uriCtrl = TextEditingController(); final _tokenCtrl = TextEditingController(); final _sharedKeyCtrl = TextEditingController(); bool _simulcast = true; bool _adaptiveStream = true; bool _dynacast = true; bool _busy = false; bool _fastConnect = false; bool _e2ee = false; @override void initState() { super.initState(); _readPrefs(); } @override void dispose() { _uriCtrl.dispose(); _tokenCtrl.dispose(); super.dispose(); } // Read saved URL and Token Future _readPrefs() async { final prefs = await SharedPreferences.getInstance(); _uriCtrl.text = const bool.hasEnvironment('URL') ? const String.fromEnvironment('URL') : prefs.getString(_storeKeyUri) ?? ''; _tokenCtrl.text = const bool.hasEnvironment('TOKEN') ? const String.fromEnvironment('TOKEN') : prefs.getString(_storeKeyToken) ?? ''; _sharedKeyCtrl.text = const bool.hasEnvironment('E2EEKEY') ? const String.fromEnvironment('E2EEKEY') : prefs.getString(_storeKeySharedKey) ?? ''; setState(() { _simulcast = prefs.getBool(_storeKeySimulcast) ?? true; _adaptiveStream = prefs.getBool(_storeKeyAdaptiveStream) ?? true; _dynacast = prefs.getBool(_storeKeyDynacast) ?? true; _fastConnect = prefs.getBool(_storeKeyFastConnect) ?? false; _e2ee = prefs.getBool(_storeKeyE2EE) ?? false; }); } // Save URL and Token Future _writePrefs() async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_storeKeyUri, _uriCtrl.text); await prefs.setString(_storeKeyToken, _tokenCtrl.text); await prefs.setString(_storeKeySharedKey, _sharedKeyCtrl.text); await prefs.setBool(_storeKeySimulcast, _simulcast); await prefs.setBool(_storeKeyAdaptiveStream, _adaptiveStream); await prefs.setBool(_storeKeyDynacast, _dynacast); await prefs.setBool(_storeKeyFastConnect, _fastConnect); await prefs.setBool(_storeKeyE2EE, _e2ee); } Future _connect(BuildContext ctx) async { // try { setState(() { _busy = true; }); // Save URL and Token for convenience await _writePrefs(); print('Connecting with url: ${_uriCtrl.text}, ' 'token: ${_tokenCtrl.text}...'); //create new room final room = Room(); // Create a Listener before connecting final listener = room.createListener(); E2EEOptions? e2eeOptions; if (_e2ee) { final keyProvider = await BaseKeyProvider.create(); e2eeOptions = E2EEOptions(keyProvider: keyProvider); var sharedKey = _sharedKeyCtrl.text; await keyProvider.setKey(sharedKey); } // Try to connect to the room // This will throw an Exception if it fails for any reason. await room.connect( _uriCtrl.text, _tokenCtrl.text, roomOptions: RoomOptions( adaptiveStream: _adaptiveStream, dynacast: _dynacast, defaultVideoPublishOptions: VideoPublishOptions( simulcast: _simulcast, ), defaultScreenShareCaptureOptions: const ScreenShareCaptureOptions(useiOSBroadcastExtension: true), e2eeOptions: e2eeOptions, ), fastConnectOptions: _fastConnect ? FastConnectOptions( microphone: const TrackOption(enabled: true), camera: const TrackOption(enabled: true), ) : null, ); await Navigator.push( ctx, MaterialPageRoute(builder: (_) => RoomPage(room, listener)), ); } catch (error) { print('Could not connect $error'); await ctx.showErrorDialog(error); } finally { setState(() { _busy = false; }); } } void _setSimulcast(bool? value) async { if (value == null || _simulcast == value) return; setState(() { _simulcast = value; }); } void _setE2EE(bool? value) async { if (value == null || _e2ee == value) return; setState(() { _e2ee = value; }); } void _setAdaptiveStream(bool? value) async { if (value == null || _adaptiveStream == value) return; setState(() { _adaptiveStream = value; }); } void _setDynacast(bool? value) async { if (value == null || _dynacast == value) return; setState(() { _dynacast = value; }); } void _setFastConnect(bool? value) async { if (value == null || _fastConnect == value) return; setState(() { _fastConnect = value; }); } @override Widget build(BuildContext context) => Scaffold( body: Container( alignment: Alignment.center, child: SingleChildScrollView( child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 20, ), constraints: const BoxConstraints(maxWidth: 400), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.only(bottom: 70), child: SvgPicture.asset( 'images/logo-dark.svg', ), ), Padding( padding: const EdgeInsets.only(bottom: 25), child: LKTextField( label: 'Server URL', ctrl: _uriCtrl, ), ), Padding( padding: const EdgeInsets.only(bottom: 25), child: LKTextField( label: 'Token', ctrl: _tokenCtrl, ), ), Padding( padding: const EdgeInsets.only(bottom: 25), child: LKTextField( label: 'Shared Key', ctrl: _sharedKeyCtrl, ), ), Padding( padding: const EdgeInsets.only(bottom: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('E2EE'), Switch( value: _e2ee, onChanged: (value) => _setE2EE(value), ), ], ), ), Padding( padding: const EdgeInsets.only(bottom: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Simulcast'), Switch( value: _simulcast, onChanged: (value) => _setSimulcast(value), ), ], ), ), Padding( padding: const EdgeInsets.only(bottom: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Adaptive Stream'), Switch( value: _adaptiveStream, onChanged: (value) => _setAdaptiveStream(value), ), ], ), ), Padding( padding: const EdgeInsets.only(bottom: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Fast Connect'), Switch( value: _fastConnect, onChanged: (value) => _setFastConnect(value), ), ], ), ), Padding( padding: const EdgeInsets.only(bottom: 25), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Dynacast'), Switch( value: _dynacast, onChanged: (value) => _setDynacast(value), ), ], ), ), ElevatedButton( onPressed: _busy ? null : () => _connect(context), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (_busy) const Padding( padding: EdgeInsets.only(right: 10), child: SizedBox( height: 15, width: 15, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ), ), const Text('CONNECT'), ], ), ), ], ), ), ), ), ); }