Skip to content
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.2.1 (2025-04-09)

* feat: Adding more components for Agents.

## 1.2.0 (2025-03-26)

* Bump version for livekit sdk.
Expand Down
86 changes: 74 additions & 12 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:livekit_components/livekit_components.dart';
import 'package:logging/logging.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
final format = DateFormat('HH:mm:ss');
Expand Down Expand Up @@ -33,18 +34,53 @@ class MyApp extends StatelessWidget {
}
}

class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
class MyHomePage extends StatefulWidget {
//
const MyHomePage({
super.key,
});

@override
State<StatefulWidget> createState() => _MyHomePageState();
}

final url = 'wss://livekit.example.com';
final token = 'your_token_here';
class _MyHomePageState extends State<MyHomePage> {
static const _storeKeyUri = 'uri';
static const _storeKeyToken = 'token';

String _url = '';
String _token = '';

void _readPrefs() async {
final prefs = await SharedPreferences.getInstance();
_url = const bool.hasEnvironment('URL')
? const String.fromEnvironment('URL')
: prefs.getString(_storeKeyUri) ?? 'your url here';
_token = const bool.hasEnvironment('TOKEN')
? const String.fromEnvironment('TOKEN')
: prefs.getString(_storeKeyToken) ?? 'your token here';
}

// Save URL and Token
Future<void> _writePrefs(String url, String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_storeKeyUri, url);
await prefs.setString(_storeKeyToken, token);
}

@override
void initState() {
super.initState();
_readPrefs();
}

/// handle join button pressed, fetch connection details and connect to room.
// ignore: unused_element
void _onJoinPressed(RoomContext roomCtx, String name, String roomName) async {
void _onJoinPressed(RoomContext roomCtx, String url, String token) async {
if (kDebugMode) {
print('Joining room: name=$name, roomName=$roomName');
print('Joining room: url=$url, token=$token');
}
await _writePrefs(url, token);
try {
await roomCtx.connect(
url: url,
Expand All @@ -61,9 +97,22 @@ class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return LivekitRoom(
roomContext: RoomContext(
url: url,
token: token,
enableAudioVisulizer: true,
onConnected: () {
if (kDebugMode) {
print('Connected to room');
}
},
onDisconnected: () {
if (kDebugMode) {
print('Disconnected from room');
}
},
onError: (error) {
if (kDebugMode) {
print('Error: $error');
}
},
),
builder: (context, roomCtx) {
var deviceScreenType = getDeviceType(MediaQuery.of(context).size);
Expand All @@ -82,8 +131,8 @@ class MyHomePage extends StatelessWidget {

/// show prejoin screen if not connected
? Prejoin(
token: token,
url: url,
token: _token,
url: _url,
onJoinPressed: _onJoinPressed,
)
:
Expand All @@ -110,9 +159,19 @@ class MyHomePage extends StatelessWidget {
),
)
: Expanded(
flex: 5,
flex: 6,
child: Stack(
children: <Widget>[
/* Expanded(
child: TranscriptionBuilder(
builder:
(context, roomCtx, transcriptions) {
return TranscriptionWidget(
transcriptions: transcriptions,
);
},
),
),*/
/// show participant loop
ParticipantLoop(
showAudioTracks: true,
Expand All @@ -137,7 +196,10 @@ class MyHomePage extends StatelessWidget {
identifier.isAudio &&
roomCtx
.enableAudioVisulizer
? const AudioVisualizerWidget()
? const AudioVisualizerWidget(
backgroundColor:
LKColors.lkDarkBlue,
)
: IsSpeakingIndicator(
builder: (context,
isSpeaking) {
Expand Down
8 changes: 4 additions & 4 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: example
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
publish_to: "none" # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
Expand Down Expand Up @@ -30,7 +30,7 @@ environment:
dependencies:
flutter:
sdk: flutter

livekit_components:
path: ..

Expand All @@ -40,9 +40,10 @@ dependencies:
intl: ^0.19.0
logging: ^1.2.0
provider: ^6.1.2
fluttertoast: ^8.2.8
fluttertoast: ^8.2.12
responsive_builder: ^0.7.1
http: ^1.2.2
shared_preferences: ^2.5.3

dev_dependencies:
flutter_test:
Expand All @@ -60,7 +61,6 @@ dev_dependencies:

# The following section is specific to Flutter packages.
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.
Expand Down
6 changes: 6 additions & 0 deletions lib/livekit_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export 'src/context/track_reference_context.dart';

export 'src/debug/logger.dart';

export 'src/types/agent_state.dart';
export 'src/types/transcription.dart';

export 'src/ui/builder/camera_preview.dart';
export 'src/ui/builder/room/chat_toggle.dart';
export 'src/ui/builder/room/chat.dart';
Expand All @@ -35,6 +38,7 @@ export 'src/ui/builder/room/room_participants.dart';
export 'src/ui/builder/room/room.dart';
export 'src/ui/builder/room/screenshare_toggle.dart';
export 'src/ui/builder/room/room_active_recording_indicator.dart';
export 'src/ui/builder/room/transcription.dart';

export 'src/ui/builder/participant/participant_track.dart';
export 'src/ui/builder/participant/participant_attributes.dart';
Expand All @@ -45,6 +49,7 @@ export 'src/ui/builder/participant/participant_name.dart';
export 'src/ui/builder/participant/participant_kind.dart';
export 'src/ui/builder/participant/participant_permissions.dart';
export 'src/ui/builder/participant/participant_transcription.dart';
export 'src/ui/builder/participant/participant_selector.dart';

export 'src/ui/builder/track/connection_quality_indicator.dart';
export 'src/ui/builder/track/e2e_encryption_indicator.dart';
Expand All @@ -64,6 +69,7 @@ export 'src/ui/widgets/room/control_bar.dart';
export 'src/ui/widgets/room/disconnect_button.dart';
export 'src/ui/widgets/room/screenshare_toggle.dart';
export 'src/ui/widgets/room/clear_pin_button.dart';
export 'src/ui/widgets/participant/transcription_widget.dart';

export 'src/ui/widgets/track/audio_visualizer_widget.dart';
export 'src/ui/widgets/track/focus_toggle.dart';
Expand Down
6 changes: 3 additions & 3 deletions lib/src/context/media_device_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ class MediaDeviceContext extends ChangeNotifier {

selectedAudioInputDeviceId ??=
Hardware.instance.selectedAudioInput?.deviceId ??
_audioInputs?.first.deviceId;
_audioInputs?.firstOrNull?.deviceId;
selectedVideoInputDeviceId ??=
Hardware.instance.selectedVideoInput?.deviceId ??
_videoInputs?.first.deviceId;
_videoInputs?.firstOrNull?.deviceId;
selectedAudioOutputDeviceId ??=
Hardware.instance.selectedAudioOutput?.deviceId ??
_audioOutputs?.first.deviceId;
_audioOutputs?.firstOrNull?.deviceId;
notifyListeners();
}

Expand Down
10 changes: 7 additions & 3 deletions lib/src/context/room_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import 'package:flutter/material.dart' hide ConnectionState;
import 'package:livekit_client/livekit_client.dart';
import 'package:provider/provider.dart';

import 'package:livekit_components/src/context/transcription_context.dart';
import '../debug/logger.dart';
import 'chat_context.dart';

class RoomContext extends ChangeNotifier with ChatContextMixin {
class RoomContext extends ChangeNotifier
with ChatContextMixin, TranscriptionContextMixin {
/// Get the [RoomContext] from the [context].
/// this method must be called under the [LivekitRoom] widget.
static RoomContext? of(BuildContext context) {
Expand Down Expand Up @@ -62,6 +64,7 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
..on<RoomConnectedEvent>((event) {
Debug.event('RoomContext: RoomConnectedEvent $roomName');
chatContextSetup(_listener, _room.localParticipant!);
transcriptionContextSetup(_listener);
_connectionState = _room.connectionState;
_connected = true;
_connecting = false;
Expand All @@ -76,6 +79,7 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
Debug.event('RoomContext: RoomDisconnectedEvent $roomName');
_connectionState = _room.connectionState;
chatContextSetup(null, null);
transcriptionContextSetup(null);
_connected = false;
_participants.clear();
onDisconnected?.call();
Expand Down Expand Up @@ -160,8 +164,8 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
}) async {
if (cameraOpened || microphoneOpened) {
_fastConnectOptions = FastConnectOptions(
microphone: TrackOption(track: localAudioTrack!),
camera: TrackOption(track: localVideoTrack!),
microphone: TrackOption(track: localAudioTrack),
camera: TrackOption(track: localVideoTrack),
);
await resetLocalTracks();
}
Expand Down
50 changes: 50 additions & 0 deletions lib/src/context/transcription_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2024 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:flutter/material.dart';

import 'package:collection/collection.dart';
import 'package:livekit_client/livekit_client.dart';

import '../debug/logger.dart';
import '../types/transcription.dart';

mixin TranscriptionContextMixin on ChangeNotifier {
final List<TranscriptionForParticipant> _transcriptions = [];
List<TranscriptionForParticipant> get transcriptions => _transcriptions;
EventsListener<RoomEvent>? _listener;

void transcriptionContextSetup(EventsListener<RoomEvent>? listener) {
_listener = listener;
if (listener != null) {
_listener!.on<TranscriptionEvent>((event) {
Debug.event('TranscriptionContext: TranscriptionEvent');
for (var segment in event.segments) {
var transcription = _transcriptions
.firstWhereOrNull((t) => t.segment.id == segment.id);
if (transcription != null) {
transcription.segment = segment;
} else {
_transcriptions
.add(TranscriptionForParticipant(segment, event.participant));
}
}
notifyListeners();
});
} else {
_listener = null;
_transcriptions.clear();
}
}
}
15 changes: 15 additions & 0 deletions lib/src/types/agent_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// Possible states for the agent participant
/// These states are set by the agent and sent via LiveKit metadata
enum AgentState {
initializing, // Agent is starting up
speaking, // Agent is speaking to the user
thinking, // Agent is processing user input
listening; // Agent is listening to user audio

static AgentState fromString(String value) {
return AgentState.values.firstWhere(
(state) => state.name == value,
orElse: () => AgentState.initializing,
);
}
}
9 changes: 9 additions & 0 deletions lib/src/types/track_identifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ class TrackIdentifier {
bool get isAudio =>
source == TrackSource.microphone ||
source == TrackSource.screenShareAudio;

bool get isVideo =>
source == TrackSource.camera || source == TrackSource.camera;

bool get isLocal => participant is LocalParticipant;

bool get hasTrack => track != null;

bool get isAgent => kind == ParticipantKind.AGENT;

ParticipantKind get kind => participant.kind;

String get name => participant.name;
}
11 changes: 11 additions & 0 deletions lib/src/types/transcription.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:livekit_client/livekit_client.dart'
show Participant, TranscriptionSegment;

class TranscriptionForParticipant {
TranscriptionForParticipant(
this.segment,
this.participant,
);
TranscriptionSegment segment;
final Participant participant;
}
Loading