前面两篇介绍了如何运行 LiveKit 官方给的示例程序,今天来介绍一下怎么修改它的 flutter 示例,使它能符合我的需要搭建出一套视频对讲系统。
首先捋一下要实现的效果,我想为家里实现一个视频对讲系统,店里没人的时候,爸妈可以在手机上远程看店,顾客可以通过屏幕与爸妈交谈。爸妈年纪都大了,所以整个系统越简单越好,界面上的按钮越少越好,然后视频会话要能自动建立,一键建立。
接下来就来简单地说一下怎么修改示例程序,使我们的需求能实现。
官方示例中的文件结构是这样的

熟悉flutter的朋友知道flutter程序里源代码在lib文件夹中。其中pages文件夹是示例程序的几个界面页,打开程序后最先看到的是connect页面,

这个页面中的所有元素都要去掉,Server URL 可以写死,Token可以设置成自动获取的其他的设置都可以采用默认的设置。
点击连接后会进入prejoin页面,这个页面里可以选择摄像头、分辨率、麦克风。这个页面也可以删除,改成默认选择前置摄像头开启麦克风。

点击JOIN后,就能展示摄像头的画面了

当多个用户同时连接时,会显示视频当前的详细属性信息。这些信息也不需要了,也要删除,下方的按钮也比较多,也要删除一些。

接下来详细讲解一下要怎么改,达到我们的目的。
首先是自动生成token,token这里需要一个room及name,room可以设置为一个常量,name对于不同的设备要不同,如果两个name相同的设备同时连接则先连接的会被踢下去。
这里name我就用设备的唯一标识符来实现,设备标识符可以用device_info_plus这个库来实现,我在程序中也参考了这个库的例子程序: https://pub.dev/packages/device_info_plus/example
对connect.dart
进行修改。connect.dart
获取token:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import 'package:device_info_plus/device_info_plus.dart'; class _ConnectPageState extends State<ConnectPage> { ... static final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); Map<String, dynamic> _deviceData = <String, dynamic>{}; bool _connected = false; Future<void> _getToken() async { await initPlatformState(); var sn=""; switch (defaultTargetPlatform) { case TargetPlatform.android: sn=_deviceData["fingerprint"]; break; case TargetPlatform.iOS: sn=_deviceData["identifierForVendor"]; break; case TargetPlatform.linux: sn=_deviceData["machineId"]; break; case TargetPlatform.windows: sn=_deviceData["deviceId"]; break; case TargetPlatform.macOS: sn=_deviceData["systemGUID"]; break; case TargetPlatform.fuchsia: break; } Dio dio = Dio(); Response response; response = await dio.get( 'http://xxx.xxx.com:8089/getToken?room=shop&name=${sn}'); _tokenCtrl.text = response.toString(); _uriCtrl.text = 'ws://xxx.xxx.com:7880'; } ... }
|
connect.dart
连接按钮部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| class _ConnectPageState extends State<ConnectPage> { ... Future<void> _connect(BuildContext ctx) async { await _getToken(); try { setState(() { _busy = true; }); await _writePrefs(); print('Connecting with url: ${_uriCtrl.text}, ' 'token: ${_tokenCtrl.text}...'); E2EEOptions? e2eeOptions; if (_e2ee) { final keyProvider = await BaseKeyProvider.create(); e2eeOptions = E2EEOptions(keyProvider: keyProvider); var sharedKey = _sharedKeyCtrl.text; await keyProvider.setSharedKey(sharedKey); } String preferredCodec = 'VP8'; final room = Room( roomOptions: RoomOptions( adaptiveStream: _adaptiveStream, dynacast: _dynacast, defaultAudioPublishOptions: const AudioPublishOptions( dtx: false, stopMicTrackOnMute: false ), defaultVideoPublishOptions: VideoPublishOptions( simulcast: _simulcast, videoCodec: preferredCodec, ), defaultScreenShareCaptureOptions: const ScreenShareCaptureOptions( useiOSBroadcastExtension: true, params: VideoParametersPresets.screenShareH720FPS15), e2eeOptions: e2eeOptions, defaultCameraCaptureOptions: const CameraCaptureOptions( maxFrameRate: 30, params: VideoParametersPresets.h540_169, ), )); final listener = room.createListener(); await room.connect( _uriCtrl.text, _tokenCtrl.text, fastConnectOptions: _fastConnect ? FastConnectOptions( microphone: const TrackOption(enabled: true), camera: const TrackOption(enabled: true), ) : null, ); await Navigator.push<void>( ctx, MaterialPageRoute(builder: (_) => RoomPage(room, listener)), ); } catch (error) { print('Could not connect $error'); await ctx.showErrorDialog(error); } finally { setState(() { _busy = false; }); } } ... }
|
connect.dart
界面部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class _ConnectPageState extends State<ConnectPage> { ... @override Widget build(BuildContext context) { if (!_connected) { _connected = true; _connect(context); } return 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: [ 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('开始'), ], ), ), ], ), ), ), ), ); } ... }
|
视频连接后的统计数据控件在participant_stats.dart
中,把对应的部分删去就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class _ParticipantStatsWidgetState extends State<ParticipantStatsWidget> { ... @override Widget build(BuildContext context) { return Container( color: Colors.black.withOpacity(0.3), padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 8, ), ); } }
|
