example app design update & show active speaker

This commit is contained in:
Hiroshi Horie
2021-10-25 02:08:26 +09:00
parent 6d4f32233f
commit d065c2eb97
11 changed files with 314 additions and 160 deletions
+16
View File
@@ -0,0 +1,16 @@
<svg width="178" height="36" viewBox="0 0 178 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="12" height="12" fill="#5BBFE4"/>
<rect y="11.9999" width="12" height="12" fill="#4A9EEB"/>
<rect y="24.0001" width="12" height="12" fill="#2D68F3"/>
<rect x="12" y="24.0001" width="12" height="12" fill="#4A9EEB"/>
<rect x="12" y="11.9999" width="12" height="12" fill="#6EE2E2"/>
<rect x="24" width="12" height="12" fill="#7AFAE1"/>
<rect x="24" y="24.0001" width="12" height="12" fill="#5BBFE4"/>
<path d="M56 6.81158H61.5723V25.9366H71.5918V30.7178H56V6.81158Z" fill="white"/>
<path d="M75.3535 12.4893H80.8203V30.7178H75.3535V12.4893ZM74.8789 7.58502C74.8789 7.1983 74.9551 6.82916 75.1074 6.4776C75.2598 6.12604 75.4766 5.82135 75.7578 5.56354C76.0508 5.29401 76.3965 5.08307 76.7949 4.93073C77.1934 4.77838 77.6387 4.70221 78.1309 4.70221C78.623 4.70221 79.0684 4.77838 79.4668 4.93073C79.8652 5.08307 80.2051 5.29401 80.4863 5.56354C80.7793 5.82135 81.002 6.12604 81.1543 6.4776C81.3066 6.82916 81.3828 7.1983 81.3828 7.58502C81.3828 7.98346 81.3066 8.35846 81.1543 8.71002C81.002 9.04987 80.7793 9.34869 80.4863 9.60651C80.2051 9.86432 79.8594 10.0694 79.4492 10.2217C79.0508 10.3741 78.6055 10.4503 78.1133 10.4503C77.6328 10.4503 77.1934 10.3741 76.7949 10.2217C76.3965 10.0694 76.0566 9.86432 75.7754 9.60651C75.4941 9.33698 75.2715 9.03229 75.1074 8.69244C74.9551 8.34088 74.8789 7.97174 74.8789 7.58502Z" fill="white"/>
<path d="M104.41 12.4893L96.7637 30.7178H91.8945L84.2656 12.4893H90.084L94.3027 23.4405H94.3379L98.5742 12.4893H104.41Z" fill="white"/>
<path d="M105.799 21.6124C105.799 20.2764 106.045 19.0225 106.537 17.8506C107.029 16.6671 107.721 15.6358 108.611 14.7569C109.514 13.878 110.58 13.1866 111.811 12.6827C113.053 12.1671 114.424 11.9092 115.924 11.9092C117.436 11.9092 118.812 12.1671 120.055 12.6827C121.309 13.1866 122.381 13.878 123.271 14.7569C124.174 15.6358 124.871 16.6671 125.363 17.8506C125.867 19.0225 126.119 20.2764 126.119 21.6124C126.119 21.9874 126.102 22.2803 126.066 22.4913C126.043 22.7022 126.014 22.9131 125.979 23.1241H111.195C111.348 23.6983 111.582 24.2139 111.898 24.671C112.227 25.128 112.602 25.5206 113.023 25.8487C113.457 26.1651 113.92 26.4112 114.412 26.587C114.916 26.7628 115.432 26.8506 115.959 26.8506C116.814 26.8506 117.559 26.669 118.191 26.3057C118.824 25.9424 119.305 25.4796 119.633 24.9171H125.504C125.234 25.7139 124.824 26.4991 124.273 27.2725C123.723 28.0342 123.043 28.7139 122.234 29.3116C121.426 29.9092 120.488 30.3897 119.422 30.753C118.367 31.1163 117.201 31.2979 115.924 31.2979C114.436 31.2979 113.07 31.046 111.828 30.5421C110.586 30.0264 109.52 29.3292 108.629 28.4503C107.738 27.5714 107.041 26.546 106.537 25.3741C106.045 24.1905 105.799 22.9366 105.799 21.6124ZM115.959 16.2159C114.822 16.2159 113.861 16.503 113.076 17.0772C112.291 17.6514 111.723 18.4073 111.371 19.3448H120.336C119.996 18.419 119.457 17.669 118.719 17.0948C117.98 16.5089 117.061 16.2159 115.959 16.2159Z" fill="white"/>
<path d="M135.857 18.835L145.104 6.81158H151.572L143.188 17.4639L151.941 30.7178H145.367L139.566 21.5596L135.857 26.253V30.7178H130.285V6.81158H135.857V18.835Z" fill="white"/>
<path d="M154.771 12.4893H160.238V30.7178H154.771V12.4893ZM154.297 7.58502C154.297 7.1983 154.373 6.82916 154.525 6.4776C154.678 6.12604 154.895 5.82135 155.176 5.56354C155.469 5.29401 155.814 5.08307 156.213 4.93073C156.611 4.77838 157.057 4.70221 157.549 4.70221C158.041 4.70221 158.486 4.77838 158.885 4.93073C159.283 5.08307 159.623 5.29401 159.904 5.56354C160.197 5.82135 160.42 6.12604 160.572 6.4776C160.725 6.82916 160.801 7.1983 160.801 7.58502C160.801 7.98346 160.725 8.35846 160.572 8.71002C160.42 9.04987 160.197 9.34869 159.904 9.60651C159.623 9.86432 159.277 10.0694 158.867 10.2217C158.469 10.3741 158.023 10.4503 157.531 10.4503C157.051 10.4503 156.611 10.3741 156.213 10.2217C155.814 10.0694 155.475 9.86432 155.193 9.60651C154.912 9.33698 154.689 9.03229 154.525 8.69244C154.373 8.34088 154.297 7.97174 154.297 7.58502Z" fill="white"/>
<path d="M168.395 7.46198L172.297 7.42682V12.4893H177.588V17.1475H172.297V23.546C172.297 24.1085 172.361 24.5772 172.49 24.9522C172.631 25.3155 172.812 25.6085 173.035 25.8311C173.27 26.0421 173.545 26.1885 173.861 26.2706C174.178 26.3526 174.518 26.3936 174.881 26.3936H177.922L177.887 30.8409H174.354C173.334 30.8409 172.367 30.753 171.453 30.5772C170.551 30.3897 169.76 30.0381 169.08 29.5225C168.4 28.9952 167.861 28.2628 167.463 27.3253C167.076 26.376 166.883 25.1339 166.883 23.5987V17.1475H163.982V12.9991L166.742 11.9444L168.395 7.46198Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

+98 -60
View File
@@ -1,5 +1,7 @@
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';
@@ -105,74 +107,110 @@ class _ConnectPageState extends State<ConnectPage> {
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Connect to LiveKit'),
),
body: Center(
body: Container(
alignment: Alignment.center,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 20,
horizontal: 20,
),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(8),
border:
Border.all(color: Theme.of(context).colorScheme.secondary),
),
constraints: const BoxConstraints(
maxWidth: 320,
),
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
enableSuggestions: false,
autocorrect: false,
controller: _uriCtrl,
decoration: const InputDecoration(labelText: 'URL'),
),
TextField(
enableSuggestions: false,
autocorrect: false,
controller: _tokenCtrl,
decoration: const InputDecoration(labelText: 'Token'),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) => _setSimulcast(value),
title: const Text('Use Simulcast'),
value: _simulcast,
),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: 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'),
],
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: 30),
child: LKTextField(
label: 'Server URL',
ctrl: _uriCtrl,
),
),
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: LKTextField(
label: 'Token',
ctrl: _tokenCtrl,
),
),
Container(
alignment: Alignment.center,
child: 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'),
],
),
),
),
// Container(
// padding: const EdgeInsets.symmetric(
// vertical: 20,
// horizontal: 20,
// ),
// decoration: BoxDecoration(
// color: Theme.of(context).cardColor,
// borderRadius: BorderRadius.circular(8),
// // border: Border.all(
// // color: Theme.of(context).colorScheme.secondary),
// ),
// constraints: const BoxConstraints(
// maxWidth: 320,
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// TextField(
// enableSuggestions: false,
// autocorrect: false,
// controller: _uriCtrl,
// decoration: const InputDecoration(labelText: 'URL'),
// ),
// TextField(
// enableSuggestions: false,
// autocorrect: false,
// controller: _tokenCtrl,
// decoration: const InputDecoration(labelText: 'Token'),
// ),
// Padding(
// padding: const EdgeInsets.only(top: 20),
// child: CheckboxListTile(
// controlAffinity: ListTileControlAffinity.leading,
// onChanged: (value) => _setSimulcast(value),
// title: const Text('Use Simulcast'),
// value: _simulcast,
// ),
// ),
//
// ],
// ),
// ),
]
// .map((e) => Padding(
// padding: const EdgeInsets.only(bottom: 50), child: e))
// .toList(),
),
],
),
),
),
);
+1 -2
View File
@@ -142,10 +142,9 @@ class _RoomPageState extends State<RoomPage> {
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: math.max(0, participants.length - 1),
itemBuilder: (BuildContext context, int index) => Container(
itemBuilder: (BuildContext context, int index) => SizedBox(
width: 100,
height: 100,
padding: const EdgeInsets.all(2),
child: ParticipantWidget(participants[index + 1],
quality: VideoQuality.LOW),
),
+30 -12
View File
@@ -7,17 +7,23 @@ import 'package:google_fonts/google_fonts.dart';
// https://github.com/flutter/flutter/issues/55092
// https://github.com/flutter/flutter/issues/39113
//
extension LKColors on Colors {
static const lkBlue = Color(0xFF5A8BFF);
static const lkDarkBlue = Color(0xFF00153C);
}
class LiveKitTheme {
//
final bgColor = Colors.black;
final textColor = Colors.white;
final cardColor = const Color(0xFF00163c);
final accentColor = const Color(0xFF2d6aef);
final cardColor = LKColors.lkDarkBlue;
final accentColor = LKColors.lkBlue;
ThemeData buildThemeData(BuildContext ctx) => ThemeData(
backgroundColor: bgColor,
// accentColor: accentColor,
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue),
// colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue),
appBarTheme: AppBarTheme(
backgroundColor: cardColor,
),
@@ -29,6 +35,14 @@ class LiveKitTheme {
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
textStyle:
MaterialStateProperty.all<TextStyle>(GoogleFonts.montserrat(
fontSize: 15,
)),
padding: MaterialStateProperty.all<EdgeInsets>(
const EdgeInsets.symmetric(vertical: 20, horizontal: 25)),
shape: MaterialStateProperty.all<OutlinedBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
// backgroundColor: MaterialStateProperty.all<Color>(accentColor),
backgroundColor: MaterialStateProperty.resolveWith((states) {
@@ -43,8 +57,13 @@ class LiveKitTheme {
checkColor: MaterialStateProperty.all(Colors.white),
fillColor: MaterialStateProperty.all(accentColor),
),
dialogBackgroundColor: cardColor,
textTheme: GoogleFonts.latoTextTheme(
dialogTheme: DialogTheme(
backgroundColor: cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
textTheme: GoogleFonts.montserratTextTheme(
Theme.of(ctx).textTheme,
).apply(
displayColor: textColor,
@@ -53,15 +72,14 @@ class LiveKitTheme {
),
hintColor: Colors.red,
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: textColor.withOpacity(.5),
labelStyle: const TextStyle(
color: LKColors.lkBlue,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: textColor.withOpacity(0.1)),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: accentColor),
hintStyle: TextStyle(
color: LKColors.lkBlue.withOpacity(.5),
),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
);
}
+64 -46
View File
@@ -151,56 +151,74 @@ class _ControlsWidgetState extends State<ControlsWidget> {
final videoPub = participant.videoTracks.firstOrNull;
final videoEnabled = videoPub != null && !videoPub.muted;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: _unpublishAll,
icon: const Icon(EvaIcons.closeCircleOutline),
),
if (canMute)
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 15,
horizontal: 15,
),
child: Wrap(
alignment: WrapAlignment.center,
spacing: 5,
runSpacing: 5,
children: [
IconButton(
onPressed: _muteAudio,
icon: const Icon(EvaIcons.mic),
)
else
IconButton(
onPressed: _unmuteAudio,
icon: const Icon(EvaIcons.micOff),
onPressed: _unpublishAll,
icon: const Icon(EvaIcons.closeCircleOutline),
tooltip: 'Unpublish all',
),
if (videoEnabled)
if (canMute)
IconButton(
onPressed: _muteAudio,
icon: const Icon(EvaIcons.mic),
tooltip: 'mute audio',
)
else
IconButton(
onPressed: _unmuteAudio,
icon: const Icon(EvaIcons.micOff),
tooltip: 'un-mute audio',
),
if (videoEnabled)
IconButton(
onPressed: _muteVideo,
icon: const Icon(EvaIcons.video),
tooltip: 'mute video',
)
else
IconButton(
onPressed: _unmuteVideo,
icon: const Icon(EvaIcons.videoOff),
tooltip: 'un-mute video',
),
IconButton(
onPressed: _muteVideo,
icon: const Icon(EvaIcons.video),
)
else
IconButton(
onPressed: _unmuteVideo,
icon: const Icon(EvaIcons.videoOff),
icon: Icon(position == CameraPosition.back
? EvaIcons.camera
: EvaIcons.person),
onPressed: () => _toggleCamera(),
tooltip: 'toggle camera',
),
IconButton(
icon: Icon(position == CameraPosition.back
? EvaIcons.camera
: EvaIcons.person),
onPressed: () => _toggleCamera(),
),
IconButton(
icon: const Icon(EvaIcons.monitor),
onPressed: () => _shareScreen(),
),
IconButton(
onPressed: _onTapDisconnect,
icon: const Icon(EvaIcons.closeCircle),
),
IconButton(
onPressed: _onTapSendData,
icon: const Icon(EvaIcons.paperPlane),
),
IconButton(
onPressed: _onTapReconnect,
icon: const Icon(EvaIcons.refresh),
),
],
IconButton(
icon: const Icon(EvaIcons.monitor),
onPressed: () => _shareScreen(),
tooltip: 'share screen (experimental)',
),
IconButton(
onPressed: _onTapDisconnect,
icon: const Icon(EvaIcons.closeCircle),
tooltip: 'disconnect',
),
IconButton(
onPressed: _onTapSendData,
icon: const Icon(EvaIcons.paperPlane),
tooltip: 'send demo data',
),
IconButton(
onPressed: _onTapReconnect,
icon: const Icon(EvaIcons.refresh),
tooltip: 're-connect',
),
],
),
);
}
}
+3 -1
View File
@@ -2,6 +2,8 @@ import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:livekit_example/theme.dart';
class NoVideoWidget extends StatelessWidget {
//
const NoVideoWidget({Key? key}) : super(key: key);
@@ -12,7 +14,7 @@ class NoVideoWidget extends StatelessWidget {
child: LayoutBuilder(
builder: (ctx, constraints) => Icon(
EvaIcons.videoOffOutline,
color: Theme.of(ctx).colorScheme.secondary,
color: LKColors.lkBlue,
size: math.min(constraints.maxHeight, constraints.maxWidth) * 0.3,
),
),
+12 -1
View File
@@ -3,6 +3,7 @@ import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_example/theme.dart';
import 'no_video.dart';
import 'participant_info.dart';
@@ -64,7 +65,17 @@ class _ParticipantWidgetState extends State<ParticipantWidget> {
@override
Widget build(BuildContext ctx) => Container(
color: Theme.of(ctx).cardColor,
foregroundDecoration: BoxDecoration(
border: widget.participant.isSpeaking
? Border.all(
width: 5,
color: LKColors.lkBlue,
)
: null,
),
decoration: BoxDecoration(
color: Theme.of(ctx).cardColor,
),
child: Stack(
children: [
// Video
+48
View File
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
class LKTextField extends StatelessWidget {
final String label;
final TextEditingController? ctrl;
const LKTextField({
required this.label,
this.ctrl,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.symmetric(
vertical: 15,
horizontal: 15,
),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.white.withOpacity(.3),
),
borderRadius: BorderRadius.circular(8),
),
child: TextField(
controller: ctrl,
decoration: const InputDecoration.collapsed(
hintText: '',
),
keyboardType: TextInputType.url,
),
),
],
);
}
+2 -2
View File
@@ -1,5 +1,5 @@
PODS:
- flutter_webrtc (0.2.2):
- flutter_webrtc (0.2.3):
- FlutterMacOS
- WebRTC-SDK (= 92.4515.10)
- FlutterMacOS (1.0.0)
@@ -30,7 +30,7 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
SPEC CHECKSUMS:
flutter_webrtc: 43d8f66a0782f8a79fad49115845b55060725b2a
flutter_webrtc: 8d69a6819b422e5caf1c7b2844a05bc6a62b437b
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f
shared_preferences_macos: 480ce071d0666e37cef23fe6c702293a3d21799e
+35
View File
@@ -97,6 +97,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "0.23.0+1"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -191,6 +198,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1+1"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1"
path_provider:
dependency: transitive
description:
@@ -226,6 +247,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
platform:
dependency: transitive
description:
@@ -392,6 +420,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.1"
sdks:
dart: ">=2.14.0 <3.0.0"
flutter: ">=2.5.0"
+5 -36
View File
@@ -13,9 +13,11 @@ dependencies:
provider: ^5.0.0
logging: ^1.0.1
shared_preferences: ^2.0.7
google_fonts: ^2.1.0
eva_icons_flutter: ^3.0.0
shared_preferences: ^2.0.7
flutter_svg: ^0.23.0+1
livekit_client:
path: ../
@@ -27,39 +29,6 @@ dev_dependencies:
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
assets:
- images/logo-dark.svg